diff --git a/resources/[standalone]/menuv/LICENSE b/resources/[standalone]/menuv/LICENSE new file mode 100644 index 0000000..e72bfdd --- /dev/null +++ b/resources/[standalone]/menuv/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/resources/[standalone]/menuv/README.md b/resources/[standalone]/menuv/README.md new file mode 100644 index 0000000..5dca70a --- /dev/null +++ b/resources/[standalone]/menuv/README.md @@ -0,0 +1,124 @@ +# MenuV | Standalone Menu for FiveM | NUI Menu +[![N|CoreV](https://i.imgur.com/iq1llQG.jpg)](https://github.com/ThymonA/menuv) + +[![Issues](https://img.shields.io/github/issues/ThymonA/menuv.svg?style=for-the-badge)](https://github.com/ThymonA/menuv/issues) +[![License](https://img.shields.io/github/license/ThymonA/menuv.svg?style=for-the-badge)](https://github.com/ThymonA/menuv/blob/master/LICENSE) +[![Forks](https://img.shields.io/github/forks/ThymonA/menuv.svg?style=for-the-badge)](https://github.com/ThymonA/menuv) +[![Stars](https://img.shields.io/github/stars/ThymonA/menuv.svg?style=for-the-badge)](https://github.com/ThymonA/menuv) +[![Discord](https://img.shields.io/badge/discord-Tigo%239999-7289da?style=for-the-badge&logo=discord)](https://discordapp.com/users/733686533873467463) + +--- + +**[MenuV](https://github.com/ThymonA/menuv)** is a library written for **[FiveM](https://fivem.net/)** and only uses NUI functionalities. This library allows you to create menus in **[FiveM](https://fivem.net/)**. This project is open-source and you must respect the [license](https://github.com/ThymonA/menuv/blob/master/LICENSE) and the hard work. + +## Features +- Support for simple buttons, sliders, checkboxes, lists and confirms +- Support for emojis on items +- Support for custom colors (RGB) +- Support for all screen resolutions. +- Item descriptions +- Rebindable keys +- Event-based callbacks +- Uses `2 msec` while menu open and idle. +- Documentation on [menuv.fivem.io/api/](https://menuv.fivem.io/api/) +- Themes: **[default](https://i.imgur.com/KSkeiQm.png)** or **[native](https://i.imgur.com/KSkeiQm.png)** + +## Compile files +**[MenuV](https://github.com/ThymonA/menuv)** uses **[VueJS](https://vuejs.org/v2/guide/installation.html#NPM)** and **[TypeScript](https://www.npmjs.com/package/typescript)** with **[NodeJS](https://nodejs.org/en/)**. If you want to use the **`master`** files, you need to build the hole project by doing: + +```sh +npm install +``` +After you have downloaded/loaded all dependencies, you can build **[MenuV](https://github.com/ThymonA/menuv)** files by executing the following command: +```sh +npm run build +``` + +After the command is executed you will see a `build` folder containing all the resource files. +Copy those files to a resource folder called `menuv` or create a symbolic link like that: + +### Windows + +```batch +mklink /J "repositoryPath\build" "fxResourcesPath\menuv" +``` + +### Linux + +```sh +ln -s "repositoryPath\build" "fxResourcesPath\menuv" +``` + +You can also check this tutorial on how to make a link: +[https://www.howtogeek.com/howto/16226/complete-guide-to-symbolic-links-symlinks-on-windows-or-linux/](https://www.howtogeek.com/howto/16226/complete-guide-to-symbolic-links-symlinks-on-windows-or-linux/) + +**When your downloading a [release](https://github.com/ThymonA/menuv/releases), you don't have to follow this step, because all [release](https://github.com/ThymonA/menuv/releases) version are build version.** + +## How to use? +> ⚠️ **example.lua** can't be added in the **menuv** fxmanifest, you must make an seperate resource like: **[menuv_example](https://github.com/ThymonA/menuv/tree/master/example)** +1. Add `start menuv` to your **server.cfg** before the resources that's uses **menuv** +2. To use **[MenuV](https://github.com/ThymonA/menuv)** you must add **@menuv/menuv.lua** in your **fxmanifest.lua** file. + +```lua +client_scripts { + '@menuv/menuv.lua', + 'example.lua' +} +``` + +### Create a menu +Create a menu by calling the **MenuV:CreateMenu** function. +```ts +MenuV:CreateMenu(title: string, subtitle: string, position: string, red: number, green: number, blue: number, texture: string, disctionary: string, namespace: string, theme: string) +``` +**Example:** +```lua +local menu = MenuV:CreateMenu('MenuV', 'Welcome to MenuV', 'topleft', 255, 0, 0, 'size-125', 'default', 'menuv', 'example_namespace', 'native') +``` + +### Create menu items +Create a item by calling **AddButton**, **AddConfirm**, **AddRange**, **AddCheckbox** or **AddSlider** in the created menu +```ts +/** CREATE A BUTTON */ +menu:AddButton({ icon: string, label: string, description: string, value: any, disabled: boolean }); + +/** CREATE A CONFIRM */ +menu:AddConfirm({ icon: string, label: string, description: string, value: boolean, disabled: boolean }); + +/** CREATE A RANGE */ +menu:AddRange({ icon: string, label: string, description: string, value: number, min: number, max: number, disabled: boolean }); + +/** CREATE A CHECKBOX */ +menu:AddCheckbox({ icon: string, label: string, description: string, value: boolean, disabled: boolean }); + +/** CREATE A SLIDER */ +menu:AddSlider({ icon: string, label: string, description: string, value: number, values: [] { label: string, value: any, description: string }, disabled: boolean }); +``` +To see example in practice, see [example.lua](https://github.com/ThymonA/menuv/blob/master/example/example.lua) + +### Events +In **[MenuV](https://github.com/ThymonA/menuv)** you can register event-based callbacks on menu and/or items. +```ts +/** REGISTER A EVENT ON MENU */ +menu:On(event: string, callback: function); + +/** REGISTER A EVENT ON ANY ITEM */ +item:On(event: string, callback: function); +``` + +## Documentation +Read **[MenuV documentation](https://menuv.fivem.io/api/)** + +## License +Project is written by **[ThymonA](https://github.com/ThymonA/)** and published under +**GNU General Public License v3.0** +[Read License](https://github.com/ThymonA/menuv/blob/master/LICENSE) + +## Screenshot +**How is this menu made?** see **[example.lua](https://github.com/ThymonA/menuv/blob/master/example/example.lua)** + + +Default | Native +:-------|:-------- +![MenuV Default](https://i.imgur.com/xGagIBm.png) | ![MenuV Native](https://i.imgur.com/KSkeiQm.png) +[Default Theme](https://i.imgur.com/xGagIBm.png) | [Native Theme](https://i.imgur.com/KSkeiQm.png) diff --git a/resources/[standalone]/menuv/VERSION b/resources/[standalone]/menuv/VERSION new file mode 100644 index 0000000..13175fd --- /dev/null +++ b/resources/[standalone]/menuv/VERSION @@ -0,0 +1 @@ +1.4.1 \ No newline at end of file diff --git a/resources/[standalone]/menuv/config.lua b/resources/[standalone]/menuv/config.lua new file mode 100644 index 0000000..c4c9c87 --- /dev/null +++ b/resources/[standalone]/menuv/config.lua @@ -0,0 +1,48 @@ +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +Config = { + Language = 'en', + HideInterval = 250, + Sounds = { + UP = { + type = 'native', + name = 'NAV_UP_DOWN', + library = 'HUD_FREEMODE_SOUNDSET' + }, + DOWN = { + type = 'native', + name = 'NAV_UP_DOWN', + library = 'HUD_FREEMODE_SOUNDSET' + }, + LEFT = { + type = 'native', + name = 'NAV_LEFT_RIGHT', + library = 'HUD_FRONTEND_DEFAULT_SOUNDSET' + }, + RIGHT = { + type = 'native', + name = 'NAV_LEFT_RIGHT', + library = 'HUD_FRONTEND_DEFAULT_SOUNDSET' + }, + ENTER = { + type = 'native', + name = 'SELECT', + library = 'HUD_FRONTEND_DEFAULT_SOUNDSET' + }, + CLOSE = { + type = 'native', + name = 'BACK', + library = 'HUD_FRONTEND_DEFAULT_SOUNDSET' + } + } +} + +_G.Config = Config +_ENV.Config = Config \ No newline at end of file diff --git a/resources/[standalone]/menuv/dist/assets/css/main.css b/resources/[standalone]/menuv/dist/assets/css/main.css new file mode 100644 index 0000000..60ad56f --- /dev/null +++ b/resources/[standalone]/menuv/dist/assets/css/main.css @@ -0,0 +1,487 @@ +/* +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +*/ +@import url('https://fonts.googleapis.com/css2?family=Epilogue:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap'); + +html, +body { + overflow: hidden; + font-family: 'Epilogue', sans-serif; + color: white; + background-color: transparent; +} + +* .hide, +html .hide, +body .hide, +div .hide, +.menuv.default.hide { + display: none !important; + opacity: 0; +} + +.menuv.default { + min-width: 20em; + max-width: 20em; + max-height: 90vh; + margin-top: 1em; + margin-left: 1em; + font-size: 0.85em; +} + +.menuv.default.topcenter { + margin-left: auto; + margin-right: auto; +} + +.menuv.default.topright { + margin-right: 1em; + margin-left: auto; +} + +.menuv.default.centerleft { + position: absolute; + top: 50%; + transform: translateY(-50%); +} + +.menuv.default.center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.menuv.default.centerright { + position: absolute; + top: 50%; + right: 1em; + transform: translateY(-50%); +} + +.menuv.default.bottomleft { + position: absolute; + bottom: 1em; +} + +.menuv.default.bottomcenter { + position: absolute; + bottom: 1em; + left: 50%; + transform: translateX(-50%); +} + +.menuv.default.bottomright { + position: absolute; + bottom: 1em; + right: 1em; +} + +.menuv.default.size-100 { + zoom: 1; +} +.menuv.default.size-110 { + zoom: 1.1; +} +.menuv.default.size-125 { + zoom: 1.25; +} +.menuv.default.size-150 { + zoom: 1.50; +} +.menuv.default.size-175 { + zoom: 1.75; +} +.menuv.default.size-200 { + zoom: 2; +} + +.menuv.default .menuv-header { + height: 3.5em; + max-height: 3.5em; + line-height: 3.5em; + width: 100%; + text-align: center; + letter-spacing: 0.25em; + font-size: 1.5em; + font-weight: 700; + background-color: black; + overflow: hidden; +} + +.menuv.default .menuv-header strong { + position: relative; + z-index: 1; + color: white; +} + +.menuv.default .menuv-header .menuv-bg-icon { + display: flex; + position: fixed; + max-height: 3.5em; + max-width: 3.5em; + overflow: hidden; + top: 0.6em; + margin-left: 9.8em; +} + +.menuv.default .menuv-header .menuv-bg-icon i, +.menuv.default .menuv-header .menuv-bg-icon svg { + font-size: 5em; + opacity: 0.5; + color: blue; +} + +.menuv.default .menuv-subheader { + background-color: blue; + text-align: left; + font-weight: 700; + font-size: 0.9em; + line-height: 2.5em; + text-transform: uppercase; + padding-left: 1em; + height: 2.5em; + max-height: 2.5em; +} + +.menuv.default .menuv-items { + background-color: rgba(0, 0, 0, 0.65); + padding-top: 0.5em; + padding-bottom: 0.5em; + max-height: 50.75vh; + overflow: hidden; + color: white; +} + +.menuv.default.size-100 .menuv-items { + max-height: 66.1vh; +} + +.menuv.default.size-110 .menuv-items { + max-height: 59.2vh; +} + +.menuv.default.size-125 .menuv-items { + max-height: 50.2vh; +} + +.menuv.default.size-150 .menuv-items { + max-height: 45.8vh; +} + +.menuv.default.size-175 .menuv-items { + max-height: 39vh; +} + +.menuv.default.size-200 .menuv-items { + max-height: 32.2vh; +} + +.menuv.default .menuv-items .menuv-item { + padding: 0.25em 0.50em; + margin: 0; + font-size: 0.9em; + max-height: auto; + height: auto; + min-height: 2em; + vertical-align: middle; + line-height: normal; + color: white; + width: 100%; + min-width: 20em; + max-width: 30em; + border-top: none !important; +} + +.menuv.default .menuv-items .menuv-item i, +.menuv.default .menuv-items .menuv-item svg { + float: right; + margin-top: 0.125em; + font-size: 1.2em; +} + +.menuv.default .menuv-items .menuv-item.active { + padding-right: 0; + padding-left: 0; +} + +.menuv.default .menuv-items .menuv-item.disabled { + opacity: 0.75; + background: #383838; + text-decoration: line-through; +} + +.menuv.default .menuv-items .menuv-item.active i, +.menuv.default .menuv-items .menuv-item.active svg { + color: black; +} + +.menuv.default .menuv-items .item-title { + word-break: break-all; +} + +.menuv.default .menuv-items .menuv-item.active .item-title { + font-weight: bold; +} + +.menuv.default .menuv-items span.menuv-icon { + margin-left: 2.5px; + margin-right: 5px; + border-right: 1px solid white; + padding-right: 5px; + float: left; + width: 2em; + text-align: center; +} + +.menuv.default .menuv-items .flex-left { + justify-content: left; +} + +.menuv.default .menuv-items span.menuv-title { + word-break: break-all; + display: inline-block; + overflow: hidden; + max-height: 2em; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 14em; +} + +.menuv.default .menuv-items .item-icon { + width: 2.5em; + max-width: 2.5em; + margin-right: 5px; +} + +.menuv.default .menuv-items .menuv-item { + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + + -ms-flex-align: center; + -webkit-align-items: center; + -webkit-box-align: center; + + align-items: center; + border-top: none !important; +} + +.menuv.default .menuv-items .menuv-item.active span.menuv-icon { + border-right: 1px solid black; +} + +.menuv.default .menuv-items span.menuv-options { + float: right; + font-size: 0.9em; + line-height: 1.85em; + text-transform: uppercase; +} + +.menuv.default .menuv-items .menuv-item.active span.menuv-options { + font-weight: 700; +} + +.menuv.default .menuv-items span.menuv-options i, +.menuv.default .menuv-items span.menuv-options svg { + float: unset; + font-size: unset; +} + +.menuv.default .menuv-items span.menuv-options i:first-child, +.menuv.default .menuv-items span.menuv-options svg:first-child { + margin-right: 0.25em; +} + +.menuv.default .menuv-items span.menuv-options i:last-child, +.menuv.default .menuv-items span.menuv-options svg:last-child { + margin-left: 0.25em; +} + +.menuv.default .menuv-items span.menuv-options span.menuv-btn { + background-color: white; + color: black; + padding: 0.25em 0.5em; + margin: 0.125em; + font-weight: bold; + font-weight: 500; + border-radius: 0.125em; +} + +.menuv.default .menuv-items .menuv-item span.menuv-options span.menuv-btn { + background-color: transparent; + color: white; +} + +.menuv.default .menuv-items .menuv-item.active span.menuv-options span.menuv-btn { + background-color: black; + color: white; +} + +.menuv.default .menuv-items span.menuv-options span.menuv-btn.active { + background-color: blue; + color: white; + font-weight: 700; +} + +.menuv.default .menuv-items .menuv-item.active span.menuv-options span.menuv-btn.active { + background-color: blue; + color: white; +} + +.menuv.default .menuv-items input[type="range"] { + display: flex; + float: right; + -webkit-appearance: none; + max-width: 6.5em; +} + +.menuv.default .menuv-items input[type="range"]:focus { + outline: none; +} + +.menuv.default .menuv-items input[type="range"]::-webkit-slider-runnable-track { + width: 100%; + height: 5px; + cursor: pointer; + box-shadow: 0px 0px 0px #000000; + background: blue; + border-radius: 0; + border: 0px solid #000000; +} + +.menuv.default .menuv-items .menuv-item.active input[type="range"]::-webkit-slider-runnable-track { + background: black; +} + +.menuv.default .menuv-items input[type="range"]::-webkit-slider-thumb { + box-shadow: 0px 0px 0px #000000; + height: 18px; + width: 5px; + border-radius: 0; + border: 1px solid white; + background: rgba(255, 255, 255, 1); + cursor: pointer; + -webkit-appearance: none; + margin-top: -7px; +} + +.menuv.default .menuv-items .menuv-item.active input[type="range"]::-webkit-slider-thumb { + background: blue; + border: 1px solid rgba(0, 0, 255, 0.25); +} + +.menuv.default .menuv-items input[type="range"]:focus::-webkit-slider-runnable-track { + background: blue; +} + +.menuv.default .menuv-items .menuv-item.active input[type="range"]:focus::-webkit-slider-runnable-track { + background: black; +} + +.menuv.default .menuv-items .menuv-desc { + display: none; + opacity: 0; + background-color: rgba(0, 0, 0, 0.65); + color: white; + position: absolute; + width: 100%; + max-width: 17.5em; + margin-left: 17.5em; + margin-top: -0.25em; + font-weight: 400; + font-size: 0.9em; + padding: 0.75em 1em; + line-height: 1.25em; + border-left: 0.375em solid blue; +} + +.menuv.default .menuv-items .menuv-item.active .menuv-desc { + display: initial; + opacity: 1; +} + +.menuv.default .menuv-items .menuv-desc strong { + color: white; +} + +.menuv.default .menuv-items .menuv-desc table { + margin-left: -0.75em; + width: calc(100% + 0.75em); +} + +.menuv.default .menuv-items .menuv-desc table th { + color: white; + padding: 2px 5px; +} + +.menuv.default .menuv-items .menuv-desc table td { + padding: 2px 5px; +} + +.menuv.default .menuv-items .menuv-label { + float: right; + font-size: 1.125em; + font-weight: 800; +} + +.menuv.default .menuv-pagination { + padding: 0.5em; + max-width: 20em; + width: 100%; + text-align: center; + position: relative; + border-top: 2px solid white; + margin-top: 1em; +} + +.menuv.default .menuv-pagination .menu-pagination-option { + display: inline-block; + height: 1.5em; + width: 3em; + background-color: white; + color: black; + text-align: center; + border-radius: 2.5px; + margin-left: 0.25em; + margin-right: 0.25em; + font-size: 0.8em; +} + +.menuv.default .menuv-pagination .menu-pagination-option.active { + background-color: red; + color: white; +} + +.menuv.default .menuv-pagination .menu-pagination-ellipsis { + display: inline-block; + height: 1.5em; + width: 1.5em; + background-color: transparent; + color: white; + text-align: center; +} + +.menuv.default .menuv-description { + background-color: rgba(0, 0, 0, 0.65); + width: 100%; + max-width: 20em; + padding: 0.5em 1em; + margin-top: 0.5em; + text-align: center; + text-transform: uppercase; +} + +.menuv.default .menuv-description strong { + color: white; + font-size: 0.8em; + font-weight: 400; +} \ No newline at end of file diff --git a/resources/[standalone]/menuv/dist/assets/css/native_theme.css b/resources/[standalone]/menuv/dist/assets/css/native_theme.css new file mode 100644 index 0000000..fb1110f --- /dev/null +++ b/resources/[standalone]/menuv/dist/assets/css/native_theme.css @@ -0,0 +1,507 @@ +/* +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +*/ +@font-face { + font-family: 'SignPainter'; + font-style: normal; + font-weight: normal; + src: local('../fonts/SignPainter'), url('../fonts/SignPainterHouseScript.woff') format('woff'); +} + +@font-face { + font-family: 'TTCommons'; + font-style: normal; + font-weight: normal; + src: local('../fonts/TTCommons'), url('../fonts/TTCommons.woff') format('woff'); +} + +html, +body { + overflow: hidden; + color: white; + background-color: transparent; +} + +* .hide, +html .hide, +body .hide, +div .hide, +.menuv.native.hide { + display: none !important; + opacity: 0; +} + +.menuv.native { + min-width: 30em; + max-width: 30em; + max-height: 90vh; + margin-top: 1em; + margin-left: 1em; + font-size: 0.85em; +} + +.menuv.native.topcenter { + margin-left: auto; + margin-right: auto; +} + +.menuv.native.topright { + margin-right: 1em; + margin-left: auto; +} + +.menuv.native.centerleft { + position: absolute; + top: 50%; + transform: translateY(-50%); +} + +.menuv.native.center { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.menuv.native.centerright { + position: absolute; + top: 50%; + right: 1em; + transform: translateY(-50%); +} + +.menuv.native.bottomleft { + position: absolute; + bottom: 1em; +} + +.menuv.native.bottomcenter { + position: absolute; + bottom: 1em; + left: 50%; + transform: translateX(-50%); +} + +.menuv.native.bottomright { + position: absolute; + bottom: 1em; + right: 1em; +} + +.menuv.native.size-100 { + zoom: 1; +} +.menuv.native.size-110 { + zoom: 1.1; +} +.menuv.native.size-125 { + zoom: 1.25; +} +.menuv.native.size-150 { + zoom: 1.50; +} +.menuv.native.size-175 { + zoom: 1.75; +} +.menuv.native.size-200 { + zoom: 2; +} + +.menuv.native .menuv-header { + height: 2em; + max-height: 4.25em; + line-height: 2.25em; + width: 100%; + text-align: center; + letter-spacing: auto; + font-size: 3.5em; + font-weight: normal; + font-family: 'SignPainter'; + background-color: black; + overflow: hidden; +} + +.menuv.native .menuv-header strong { + position: relative; + z-index: 1; + color: white; + max-width: 1em; + font-weight: normal; +} + +.menuv.native .menuv-header .menuv-bg-icon { + display: flex; + position: fixed; + max-height: 4.25em; + max-width: 4.25em; + overflow: hidden; + top: 0.6em; + margin-left: 9.8em; +} + +.menuv.native .menuv-header .menuv-bg-icon i, +.menuv.native .menuv-header .menuv-bg-icon svg { + font-size: 5em; + opacity: 0.5; + color: blue; +} + +.menuv.native .menuv-subheader { + background-color: black !important; + text-align: left; + font-weight: 500; + font-size: 1.125em; + line-height: auto; + text-transform: uppercase; + padding-top: 0.125em; + padding-bottom: 0.375em; + padding-left: 0.5em; + height: auto; + color: #2e69bb !important; +} + +.menuv.native .menuv-items { + background-color: rgba(0, 0, 0, 0.65); + padding-bottom: 0.5em; + max-height: 50.75vh; + overflow: hidden; + color: white; +} + +.menuv.native.size-100 .menuv-items { + max-height: 66.1vh; +} + +.menuv.native.size-110 .menuv-items { + max-height: 59.2vh; +} + +.menuv.native.size-125 .menuv-items { + max-height: 50.2vh; +} + +.menuv.native.size-150 .menuv-items { + max-height: 45.8vh; +} + +.menuv.native.size-175 .menuv-items { + max-height: 39vh; +} + +.menuv.native.size-200 .menuv-items { + max-height: 32.2vh; +} + +.menuv.native .menuv-items .menuv-item { + padding: 0.25em 0.50em; + margin: 0; + font-size: 1em; + max-height: auto; + height: auto; + line-height: 1.25em; + color: white; + width: 100%; + min-width: 100%; + max-width: 30em; +} + +.menuv.native .menuv-items .flex-left { + justify-content: left; +} + +.menuv.native .menuv-items .item-title { + font-family: 'TTCommons'; + font-weight: 400; + font-size: 1.35em; + padding-top: 0.25em; + word-break: break-all; +} + +.menuv.native .menuv-items .item-icon { + width: 2.5em; + max-width: 2.5em; + margin-right: 5px; +} + +.menuv.native .menuv-items .menuv-item.disabled .item-icon { + text-decoration: none !important; +} + +.menuv.native .menuv-items .menuv-item i, +.menuv.native .menuv-items .menuv-item svg { + float: right; + margin-top: 0.125em; + font-size: 1.2em; +} + +.menuv.native .menuv-items .menuv-item.active { + padding-right: 0; + padding-left: 0; +} + +.menuv.native .menuv-items .menuv-item.disabled { + opacity: 0.75; + background: #383838; +} + +.menuv.native .menuv-items .menuv-item.active i, +.menuv.native .menuv-items .menuv-item.active svg { + color: black; +} + +.menuv.native .menuv-items span.menuv-icon { + margin-left: 2.5px; + margin-right: 5px; + border-right: 1px solid white; + padding-right: 5px; + float: left; + width: 2em; + text-align: center; +} + +.menuv.native .menuv-items span.menuv-title { + word-break: break-all; + display: inline-block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-size: 1.25em; + font-weight: 600; + letter-spacing: normal; + padding: none; + margin: none; +} + +.menuv.native .menuv-items .menuv-item.active span.menuv-icon { + border-right: 1px solid black; +} + +.menuv.native .menuv-items .menuv-item { + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + + -ms-flex-align: center; + -webkit-align-items: center; + -webkit-box-align: center; + + align-items: center; + border-top: none !important; +} + +.menuv.native .menuv-items span.menuv-options { + float: right; + font-size: 0.9em; + vertical-align: middle; + text-transform: uppercase; +} + +.menuv.native .menuv-items .menuv-item.active span.menuv-options { + font-weight: 700; +} + +.menuv.native .menuv-items span.menuv-options i, +.menuv.native .menuv-items span.menuv-options svg { + float: unset; + font-size: unset; +} + +.menuv.native .menuv-items span.menuv-options i:first-child, +.menuv.native .menuv-items span.menuv-options svg:first-child { + margin-right: 0.25em; +} + +.menuv.native .menuv-items span.menuv-options i:last-child, +.menuv.native .menuv-items span.menuv-options svg:last-child { + margin-left: 0.25em; +} + +.menuv.native .menuv-items span.menuv-options span.menuv-btn { + background-color: white; + color: black; + padding: 0.25em 0.5em; + margin: 0.125em; + font-weight: bold; + font-weight: 500; + border-radius: 0.125em; +} + +.menuv.native .menuv-items .menuv-item span.menuv-options span.menuv-btn { + background-color: transparent; + color: white; +} + +.menuv.native .menuv-items .menuv-item.active span.menuv-options span.menuv-btn { + background-color: black; + color: white; +} + +.menuv.native .menuv-items span.menuv-options span.menuv-btn.active { + background-color: blue; + color: white; + font-weight: 700; +} + +.menuv.native .menuv-items .menuv-item.active span.menuv-options span.menuv-btn.active { + background-color: blue; + color: white; +} + +.menuv.native .menuv-items input[type="range"] { + display: flex; + float: right; + -webkit-appearance: none; + max-width: 6.5em; + margin-top: 0.15em; +} + +.menuv.native .menuv-items input[type="range"]:focus { + outline: none; +} + +.menuv.native .menuv-items input[type="range"]::-webkit-slider-runnable-track { + width: 100%; + height: 5px; + cursor: pointer; + box-shadow: 0px 0px 0px #000000; + background: blue; + border-radius: 0; + border: 0px solid #000000; +} + +.menuv.native .menuv-items .menuv-item.active input[type="range"]::-webkit-slider-runnable-track { + background: black; +} + +.menuv.native .menuv-items input[type="range"]::-webkit-slider-thumb { + box-shadow: 0px 0px 0px #000000; + height: 18px; + width: 5px; + border-radius: 0; + border: 1px solid white; + background: rgba(255, 255, 255, 1); + cursor: pointer; + -webkit-appearance: none; + margin-top: -7px; +} + +.menuv.native .menuv-items .menuv-item.active input[type="range"]::-webkit-slider-thumb { + background: blue; + border: 1px solid rgba(0, 0, 255, 0.25); +} + +.menuv.native .menuv-items input[type="range"]:focus::-webkit-slider-runnable-track { + background: blue; +} + +.menuv.native .menuv-items .menuv-item.active input[type="range"]:focus::-webkit-slider-runnable-track { + background: black; +} + +.menuv.native .menuv-items .menuv-desc { + display: none; + opacity: 0; + background-color: rgba(0, 0, 0, 0.65); + color: white; + position: absolute; + width: 100%; + max-width: 17.5em; + margin-left: 17.5em; + margin-top: -0.25em; + font-weight: 400; + font-size: 0.9em; + padding: 0.75em 1em; + line-height: 1.25em; + border-left: 0.375em solid blue; +} + +.menuv.native .menuv-items .menuv-item.active .menuv-desc { + display: initial; + opacity: 1; +} + +.menuv.native .menuv-items .menuv-desc strong { + color: white; +} + +.menuv.native .menuv-items .menuv-desc table { + margin-left: -0.75em; + width: calc(100% + 0.75em); +} + +.menuv.native .menuv-items .menuv-desc table th { + color: white; + padding: 2px 5px; +} + +.menuv.native .menuv-items .menuv-desc table td { + padding: 2px 5px; +} + +.menuv.native .menuv-items .menuv-label { + float: right; + font-size: 1.125em; + font-weight: 800; +} + +.menuv.native .menuv-pagination { + padding: 0.5em; + max-width: 20em; + width: 100%; + text-align: center; + position: relative; + border-top: 2px solid white; + margin-top: 1em; +} + +.menuv.native .menuv-pagination .menu-pagination-option { + display: inline-block; + height: 1.5em; + width: 3em; + background-color: white; + color: black; + text-align: center; + border-radius: 2.5px; + margin-left: 0.25em; + margin-right: 0.25em; + font-size: 0.8em; +} + +.menuv.native .menuv-pagination .menu-pagination-option.active { + background-color: red; + color: white; +} + +.menuv.native .menuv-pagination .menu-pagination-ellipsis { + display: inline-block; + height: 1.5em; + width: 1.5em; + background-color: transparent; + color: white; + text-align: center; +} + +.menuv.native .menuv-description { + border-top: 2px solid black; + background-color: rgba(0, 0, 0, 0.65); + width: 100%; + max-width: 30em; + padding: 0.5em 1em; + margin-top: 0.5em; + text-align: left; +} + +.menuv.native .menuv-description strong { + color: white; + font-family: 'TTCommons'; + font-weight: 400; + font-size: 1.15em; +} \ No newline at end of file diff --git a/resources/[standalone]/menuv/dist/assets/fonts/SignPainterHouseScript.woff b/resources/[standalone]/menuv/dist/assets/fonts/SignPainterHouseScript.woff new file mode 100644 index 0000000..3cc8ffb Binary files /dev/null and b/resources/[standalone]/menuv/dist/assets/fonts/SignPainterHouseScript.woff differ diff --git a/resources/[standalone]/menuv/dist/assets/fonts/TTCommons.woff b/resources/[standalone]/menuv/dist/assets/fonts/TTCommons.woff new file mode 100644 index 0000000..0b4fb47 Binary files /dev/null and b/resources/[standalone]/menuv/dist/assets/fonts/TTCommons.woff differ diff --git a/resources/[standalone]/menuv/dist/assets/js/menuv.js b/resources/[standalone]/menuv/dist/assets/js/menuv.js new file mode 100644 index 0000000..2f3674d --- /dev/null +++ b/resources/[standalone]/menuv/dist/assets/js/menuv.js @@ -0,0 +1,2 @@ +/*! For license information please see menuv.js.LICENSE.txt */ +(()=>{var t={81:function(t){t.exports=function(){"use strict";function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}function e(){return(e=Object.assign||function(t){for(var e=1;e=.001?function(t,e,n,r){for(var i=0;i<4;++i){var o=u(e,n,r);if(0===o)return e;e-=(a(e,n,r)-t)/o}return e}(e,c,t,i):0===l?c:function(t,e,n,r,i){var o,s,u=0;do{(o=a(s=e+(n-e)/2,r,i)-t)>0?n=s:e=s}while(Math.abs(o)>1e-7&&++u<10);return s}(e,r,r+n,t,i)}return function(t){return 0===t?0:1===t?1:a(d(t),e,o)}},d={ease:[.25,.1,.25,1],linear:[0,0,1,1],"ease-in":[.42,0,1,1],"ease-out":[0,0,.58,1],"ease-in-out":[.42,0,.58,1]},f=!1;try{var p=Object.defineProperty({},"passive",{get:function(){f=!0}});window.addEventListener("test",null,p)}catch(t){}var h=function(t){return"string"!=typeof t?t:document.querySelector(t)},v=function(t,e,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{passive:!1};e instanceof Array||(e=[e]);for(var i=0;i2&&void 0!==arguments[2]?arguments[2]:{};if("object"===t(y)?w=y:"number"==typeof y&&(w.duration=y),!(e=h(m)))return console.warn("[vue-scrollto warn]: Trying to scroll to an element that is not on the page: "+m);if(n=h(w.container||g.container),r=w.hasOwnProperty("duration")?w.duration:g.duration,o=w.hasOwnProperty("lazy")?w.lazy:g.lazy,i=w.easing||g.easing,s=w.hasOwnProperty("offset")?w.offset:g.offset,a=w.hasOwnProperty("force")?!1!==w.force:g.force,u=w.hasOwnProperty("cancelable")?!1!==w.cancelable:g.cancelable,c=w.onStart||g.onStart,f=w.onDone||g.onDone,p=w.onCancel||g.onCancel,b=void 0===w.x?g.x:w.x,E=void 0===w.y?g.y:w.y,"function"==typeof s&&(s=s(e,n)),x=j(n),T=M(n),U(),k=!1,!a){var A="body"===n.tagName.toLowerCase()?document.documentElement.clientHeight||window.innerHeight:n.offsetHeight,$=T,P=$+A,R=O-s,I=R+e.offsetHeight;if(R>=$&&I<=P)return void(f&&f(e))}if(c&&c(e),S||C)return"string"==typeof i&&(i=d[i]||d.ease),L=l.apply(l,i),v(n,_,D,{passive:!0}),window.requestAnimationFrame(F),function(){N=null,k=!0};f&&f(e)}},x=E(),w=[];function T(t){var e=function(t){for(var e=0;e{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var r in e)n.o(e,r)&&!n.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{"use strict";var t=Object.freeze({});function e(t){return null==t}function r(t){return null!=t}function i(t){return!0===t}function o(t){return"string"==typeof t||"number"==typeof t||"symbol"==typeof t||"boolean"==typeof t}function s(t){return null!==t&&"object"==typeof t}var a=Object.prototype.toString;function u(t){return"[object Object]"===a.call(t)}function c(t){var e=parseFloat(String(t));return e>=0&&Math.floor(e)===e&&isFinite(t)}function l(t){return r(t)&&"function"==typeof t.then&&"function"==typeof t.catch}function d(t){return null==t?"":Array.isArray(t)||u(t)&&t.toString===a?JSON.stringify(t,null,2):String(t)}function f(t){var e=parseFloat(t);return isNaN(e)?t:e}function p(t,e){for(var n=Object.create(null),r=t.split(","),i=0;i-1)return t.splice(n,1)}}var m=Object.prototype.hasOwnProperty;function y(t,e){return m.call(t,e)}function _(t){var e=Object.create(null);return function(n){return e[n]||(e[n]=t(n))}}var g=/-(\w)/g,b=_((function(t){return t.replace(g,(function(t,e){return e?e.toUpperCase():""}))})),E=_((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),x=/\B([A-Z])/g,w=_((function(t){return t.replace(x,"-$1").toLowerCase()})),T=Function.prototype.bind?function(t,e){return t.bind(e)}:function(t,e){function n(n){var r=arguments.length;return r?r>1?t.apply(e,arguments):t.call(e,n):t.call(e)}return n._length=t.length,n};function O(t,e){e=e||0;for(var n=t.length-e,r=new Array(n);n--;)r[n]=t[n+e];return r}function C(t,e){for(var n in e)t[n]=e[n];return t}function S(t){for(var e={},n=0;n0,W=B&&B.indexOf("edge/")>0,q=(B&&B.indexOf("android"),B&&/iphone|ipad|ipod|ios/.test(B)||"ios"===V),Y=(B&&/chrome\/\d+/.test(B),B&&/phantomjs/.test(B),B&&B.match(/firefox\/(\d+)/)),J={}.watch,Z=!1;if(H)try{var Q={};Object.defineProperty(Q,"passive",{get:function(){Z=!0}}),window.addEventListener("test-passive",null,Q)}catch(t){}var tt=function(){return void 0===U&&(U=!H&&!z&&void 0!==n.g&&n.g.process&&"server"===n.g.process.env.VUE_ENV),U},et=H&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function nt(t){return"function"==typeof t&&/native code/.test(t.toString())}var rt,it="undefined"!=typeof Symbol&&nt(Symbol)&&"undefined"!=typeof Reflect&&nt(Reflect.ownKeys);rt="undefined"!=typeof Set&&nt(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var ot=k,st=0,at=function(){this.id=st++,this.subs=[]};at.prototype.addSub=function(t){this.subs.push(t)},at.prototype.removeSub=function(t){v(this.subs,t)},at.prototype.depend=function(){at.target&&at.target.addDep(this)},at.prototype.notify=function(){for(var t=this.subs.slice(),e=0,n=t.length;e-1)if(o&&!y(i,"default"))s=!1;else if(""===s||s===w(t)){var u=jt(String,i.type);(u<0||a0&&(ue((a=ce(a,(n||"")+"_"+s))[0])&&ue(c)&&(l[u]=ht(c.text+a[0].text),a.shift()),l.push.apply(l,a)):o(a)?ue(c)?l[u]=ht(c.text+a):""!==a&&l.push(ht(a)):ue(a)&&ue(c)?l[u]=ht(c.text+a.text):(i(t._isVList)&&r(a.tag)&&e(a.key)&&r(n)&&(a.key="__vlist"+n+"_"+s+"__"),l.push(a)));return l}function le(t,e){if(t){for(var n=Object.create(null),r=it?Reflect.ownKeys(t):Object.keys(t),i=0;i0,s=e?!!e.$stable:!o,a=e&&e.$key;if(e){if(e._normalized)return e._normalized;if(s&&r&&r!==t&&a===r.$key&&!o&&!r.$hasNormal)return r;for(var u in i={},e)e[u]&&"$"!==u[0]&&(i[u]=he(n,u,e[u]))}else i={};for(var c in n)c in i||(i[c]=ve(n,c));return e&&Object.isExtensible(e)&&(e._normalized=i),j(i,"$stable",s),j(i,"$key",a),j(i,"$hasNormal",o),i}function he(t,e,n){var r=function(){var t=arguments.length?n.apply(null,arguments):n({});return(t=t&&"object"==typeof t&&!Array.isArray(t)?[t]:ae(t))&&(0===t.length||1===t.length&&t[0].isComment)?void 0:t};return n.proxy&&Object.defineProperty(t,e,{get:r,enumerable:!0,configurable:!0}),r}function ve(t,e){return function(){return t[e]}}function me(t,e){var n,i,o,a,u;if(Array.isArray(t)||"string"==typeof t)for(n=new Array(t.length),i=0,o=t.length;idocument.createEvent("Event").timeStamp&&(cn=function(){return ln.now()})}function dn(){var t,e;for(un=cn(),sn=!0,en.sort((function(t,e){return t.id-e.id})),an=0;anan&&en[n].id>t.id;)n--;en.splice(n+1,0,t)}else en.push(t);on||(on=!0,Zt(dn))}}(this)},pn.prototype.run=function(){if(this.active){var t=this.get();if(t!==this.value||s(t)||this.deep){var e=this.value;if(this.value=t,this.user)try{this.cb.call(this.vm,t,e)}catch(t){Ut(t,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,t,e)}}},pn.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},pn.prototype.depend=function(){for(var t=this.deps.length;t--;)this.deps[t].depend()},pn.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||v(this.vm._watchers,this);for(var t=this.deps.length;t--;)this.deps[t].removeSub(this);this.active=!1}};var hn={enumerable:!0,configurable:!0,get:k,set:k};function vn(t,e,n){hn.get=function(){return this[e][n]},hn.set=function(t){this[e][n]=t},Object.defineProperty(t,n,hn)}var mn={lazy:!0};function yn(t,e,n){var r=!tt();"function"==typeof n?(hn.get=r?_n(e):gn(n),hn.set=k):(hn.get=n.get?r&&!1!==n.cache?_n(e):gn(n.get):k,hn.set=n.set||k),Object.defineProperty(t,e,hn)}function _n(t){return function(){var e=this._computedWatchers&&this._computedWatchers[t];if(e)return e.dirty&&e.evaluate(),at.target&&e.depend(),e.value}}function gn(t){return function(){return t.call(this,this)}}function bn(t,e,n,r){return u(n)&&(r=n,n=n.handler),"string"==typeof n&&(n=t[n]),t.$watch(e,n,r)}var En=0;function xn(t){var e=t.options;if(t.super){var n=xn(t.super);if(n!==t.superOptions){t.superOptions=n;var r=function(t){var e,n=t.options,r=t.sealedOptions;for(var i in n)n[i]!==r[i]&&(e||(e={}),e[i]=n[i]);return e}(t);r&&C(t.extendOptions,r),(e=t.options=Pt(n,t.extendOptions)).name&&(e.components[e.name]=t)}}return e}function wn(t){this._init(t)}function Tn(t){return t&&(t.Ctor.options.name||t.tag)}function On(t,e){return Array.isArray(t)?t.indexOf(e)>-1:"string"==typeof t?t.split(",").indexOf(e)>-1:(n=t,!("[object RegExp]"!==a.call(n))&&t.test(e));var n}function Cn(t,e){var n=t.cache,r=t.keys,i=t._vnode;for(var o in n){var s=n[o];if(s){var a=Tn(s.componentOptions);a&&!e(a)&&Sn(n,o,r,i)}}}function Sn(t,e,n,r){var i=t[e];!i||r&&i.tag===r.tag||i.componentInstance.$destroy(),t[e]=null,v(n,e)}!function(e){e.prototype._init=function(e){var n=this;n._uid=En++,n._isVue=!0,e&&e._isComponent?function(t,e){var n=t.$options=Object.create(t.constructor.options),r=e._parentVnode;n.parent=e.parent,n._parentVnode=r;var i=r.componentOptions;n.propsData=i.propsData,n._parentListeners=i.listeners,n._renderChildren=i.children,n._componentTag=i.tag,e.render&&(n.render=e.render,n.staticRenderFns=e.staticRenderFns)}(n,e):n.$options=Pt(xn(n.constructor),e||{},n),n._renderProxy=n,n._self=n,function(t){var e=t.$options,n=e.parent;if(n&&!e.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(t)}t.$parent=n,t.$root=n?n.$root:t,t.$children=[],t.$refs={},t._watcher=null,t._inactive=null,t._directInactive=!1,t._isMounted=!1,t._isDestroyed=!1,t._isBeingDestroyed=!1}(n),function(t){t._events=Object.create(null),t._hasHookEvent=!1;var e=t.$options._parentListeners;e&&We(t,e)}(n),function(e){e._vnode=null,e._staticTrees=null;var n=e.$options,r=e.$vnode=n._parentVnode,i=r&&r.context;e.$slots=de(n._renderChildren,i),e.$scopedSlots=t,e._c=function(t,n,r,i){return je(e,t,n,r,i,!1)},e.$createElement=function(t,n,r,i){return je(e,t,n,r,i,!0)};var o=r&&r.data;wt(e,"$attrs",o&&o.attrs||t,null,!0),wt(e,"$listeners",n._parentListeners||t,null,!0)}(n),tn(n,"beforeCreate"),function(t){var e=le(t.$options.inject,t);e&&(bt(!1),Object.keys(e).forEach((function(n){wt(t,n,e[n])})),bt(!0))}(n),function(t){t._watchers=[];var e=t.$options;e.props&&function(t,e){var n=t.$options.propsData||{},r=t._props={},i=t.$options._propKeys=[];t.$parent&&bt(!1);var o=function(o){i.push(o);var s=It(o,e,n,t);wt(r,o,s),o in t||vn(t,"_props",o)};for(var s in e)o(s);bt(!0)}(t,e.props),e.methods&&function(t,e){for(var n in t.$options.props,e)t[n]="function"!=typeof e[n]?k:T(e[n],t)}(t,e.methods),e.data?function(t){var e=t.$options.data;u(e=t._data="function"==typeof e?function(t,e){ct();try{return t.call(e,e)}catch(t){return Ut(t,e,"data()"),{}}finally{lt()}}(e,t):e||{})||(e={});for(var n,r=Object.keys(e),i=t.$options.props,o=(t.$options.methods,r.length);o--;){var s=r[o];i&&y(i,s)||(n=void 0,36===(n=(s+"").charCodeAt(0))||95===n)||vn(t,"_data",s)}xt(e,!0)}(t):xt(t._data={},!0),e.computed&&function(t,e){var n=t._computedWatchers=Object.create(null),r=tt();for(var i in e){var o=e[i],s="function"==typeof o?o:o.get;r||(n[i]=new pn(t,s||k,k,mn)),i in t||yn(t,i,o)}}(t,e.computed),e.watch&&e.watch!==J&&function(t,e){for(var n in e){var r=e[n];if(Array.isArray(r))for(var i=0;i1?O(n):n;for(var r=O(arguments,1),i='event handler for "'+t+'"',o=0,s=n.length;oparseInt(this.max)&&Sn(s,a[0],a,this._vnode)),e.data.keepAlive=!0}return e||t&&t[0]}}};!function(t){var e={get:function(){return M}};Object.defineProperty(t,"config",e),t.util={warn:ot,extend:C,mergeOptions:Pt,defineReactive:wt},t.set=Tt,t.delete=Ot,t.nextTick=Zt,t.observable=function(t){return xt(t),t},t.options=Object.create(null),I.forEach((function(e){t.options[e+"s"]=Object.create(null)})),t.options._base=t,C(t.options.components,An),function(t){t.use=function(t){var e=this._installedPlugins||(this._installedPlugins=[]);if(e.indexOf(t)>-1)return this;var n=O(arguments,1);return n.unshift(this),"function"==typeof t.install?t.install.apply(t,n):"function"==typeof t&&t.apply(null,n),e.push(t),this}}(t),function(t){t.mixin=function(t){return this.options=Pt(this.options,t),this}}(t),function(t){t.cid=0;var e=1;t.extend=function(t){t=t||{};var n=this,r=n.cid,i=t._Ctor||(t._Ctor={});if(i[r])return i[r];var o=t.name||n.options.name,s=function(t){this._init(t)};return(s.prototype=Object.create(n.prototype)).constructor=s,s.cid=e++,s.options=Pt(n.options,t),s.super=n,s.options.props&&function(t){var e=t.options.props;for(var n in e)vn(t.prototype,"_props",n)}(s),s.options.computed&&function(t){var e=t.options.computed;for(var n in e)yn(t.prototype,n,e[n])}(s),s.extend=n.extend,s.mixin=n.mixin,s.use=n.use,I.forEach((function(t){s[t]=n[t]})),o&&(s.options.components[o]=s),s.superOptions=n.options,s.extendOptions=t,s.sealedOptions=C({},s.options),i[r]=s,s}}(t),function(t){I.forEach((function(e){t[e]=function(t,n){return n?("component"===e&&u(n)&&(n.name=n.name||t,n=this.options._base.extend(n)),"directive"===e&&"function"==typeof n&&(n={bind:n,update:n}),this.options[e+"s"][t]=n,n):this.options[e+"s"][t]}}))}(t)}(wn),Object.defineProperty(wn.prototype,"$isServer",{get:tt}),Object.defineProperty(wn.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(wn,"FunctionalRenderContext",{value:Ne}),wn.version="2.6.12";var $n=p("style,class"),Nn=p("input,textarea,option,select,progress"),Ln=p("contenteditable,draggable,spellcheck"),Pn=p("events,caret,typing,plaintext-only"),Rn=p("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),In="http://www.w3.org/1999/xlink",Dn=function(t){return":"===t.charAt(5)&&"xlink"===t.slice(0,5)},Mn=function(t){return Dn(t)?t.slice(6,t.length):""},jn=function(t){return null==t||!1===t};function Un(t,e){return{staticClass:Fn(t.staticClass,e.staticClass),class:r(t.class)?[t.class,e.class]:e.class}}function Fn(t,e){return t?e?t+" "+e:t:e||""}function Xn(t){return Array.isArray(t)?function(t){for(var e,n="",i=0,o=t.length;i-1?lr(t,e,n):Rn(e)?jn(n)?t.removeAttribute(e):(n="allowfullscreen"===e&&"EMBED"===t.tagName?"true":e,t.setAttribute(e,n)):Ln(e)?t.setAttribute(e,function(t,e){return jn(e)||"false"===e?"false":"contenteditable"===t&&Pn(e)?e:"true"}(e,n)):Dn(e)?jn(n)?t.removeAttributeNS(In,Mn(e)):t.setAttributeNS(In,e,n):lr(t,e,n)}function lr(t,e,n){if(jn(n))t.removeAttribute(e);else{if(G&&!K&&"TEXTAREA"===t.tagName&&"placeholder"===e&&""!==n&&!t.__ieph){var r=function(e){e.stopImmediatePropagation(),t.removeEventListener("input",r)};t.addEventListener("input",r),t.__ieph=!0}t.setAttribute(e,n)}}var dr={create:ur,update:ur};function fr(t,n){var i=n.elm,o=n.data,s=t.data;if(!(e(o.staticClass)&&e(o.class)&&(e(s)||e(s.staticClass)&&e(s.class)))){var a=function(t){for(var e=t.data,n=t,i=t;r(i.componentInstance);)(i=i.componentInstance._vnode)&&i.data&&(e=Un(i.data,e));for(;r(n=n.parent);)n&&n.data&&(e=Un(e,n.data));return o=e.staticClass,s=e.class,r(o)||r(s)?Fn(o,Xn(s)):"";var o,s}(n),u=i._transitionClasses;r(u)&&(a=Fn(a,Xn(u))),a!==i._prevClass&&(i.setAttribute("class",a),i._prevClass=a)}}var pr,hr={create:fr,update:fr};function vr(t,e,n){var r=pr;return function i(){var o=e.apply(null,arguments);null!==o&&_r(t,i,n,r)}}var mr=Vt&&!(Y&&Number(Y[1])<=53);function yr(t,e,n,r){if(mr){var i=un,o=e;e=o._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=i||t.timeStamp<=0||t.target.ownerDocument!==document)return o.apply(this,arguments)}}pr.addEventListener(t,e,Z?{capture:n,passive:r}:n)}function _r(t,e,n,r){(r||pr).removeEventListener(t,e._wrapper||e,n)}function gr(t,n){if(!e(t.data.on)||!e(n.data.on)){var i=n.data.on||{},o=t.data.on||{};pr=n.elm,function(t){if(r(t.__r)){var e=G?"change":"input";t[e]=[].concat(t.__r,t[e]||[]),delete t.__r}r(t.__c)&&(t.change=[].concat(t.__c,t.change||[]),delete t.__c)}(i),ie(i,o,yr,_r,vr,n.context),pr=void 0}}var br,Er={create:gr,update:gr};function xr(t,n){if(!e(t.data.domProps)||!e(n.data.domProps)){var i,o,s=n.elm,a=t.data.domProps||{},u=n.data.domProps||{};for(i in r(u.__ob__)&&(u=n.data.domProps=C({},u)),a)i in u||(s[i]="");for(i in u){if(o=u[i],"textContent"===i||"innerHTML"===i){if(n.children&&(n.children.length=0),o===a[i])continue;1===s.childNodes.length&&s.removeChild(s.childNodes[0])}if("value"===i&&"PROGRESS"!==s.tagName){s._value=o;var c=e(o)?"":String(o);wr(s,c)&&(s.value=c)}else if("innerHTML"===i&&Vn(s.tagName)&&e(s.innerHTML)){(br=br||document.createElement("div")).innerHTML=""+o+"";for(var l=br.firstChild;s.firstChild;)s.removeChild(s.firstChild);for(;l.firstChild;)s.appendChild(l.firstChild)}else if(o!==a[i])try{s[i]=o}catch(t){}}}}function wr(t,e){return!t.composing&&("OPTION"===t.tagName||function(t,e){var n=!0;try{n=document.activeElement!==t}catch(t){}return n&&t.value!==e}(t,e)||function(t,e){var n=t.value,i=t._vModifiers;if(r(i)){if(i.number)return f(n)!==f(e);if(i.trim)return n.trim()!==e.trim()}return n!==e}(t,e))}var Tr={create:xr,update:xr},Or=_((function(t){var e={},n=/:(.+)/;return t.split(/;(?![^(]*\))/g).forEach((function(t){if(t){var r=t.split(n);r.length>1&&(e[r[0].trim()]=r[1].trim())}})),e}));function Cr(t){var e=Sr(t.style);return t.staticStyle?C(t.staticStyle,e):e}function Sr(t){return Array.isArray(t)?S(t):"string"==typeof t?Or(t):t}var kr,Ar=/^--/,$r=/\s*!important$/,Nr=function(t,e,n){if(Ar.test(e))t.style.setProperty(e,n);else if($r.test(n))t.style.setProperty(w(e),n.replace($r,""),"important");else{var r=Pr(e);if(Array.isArray(n))for(var i=0,o=n.length;i-1?e.split(Dr).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var n=" "+(t.getAttribute("class")||"")+" ";n.indexOf(" "+e+" ")<0&&t.setAttribute("class",(n+e).trim())}}function jr(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(" ")>-1?e.split(Dr).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute("class");else{for(var n=" "+(t.getAttribute("class")||"")+" ",r=" "+e+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?t.setAttribute("class",n):t.removeAttribute("class")}}function Ur(t){if(t){if("object"==typeof t){var e={};return!1!==t.css&&C(e,Fr(t.name||"v")),C(e,t),e}return"string"==typeof t?Fr(t):void 0}}var Fr=_((function(t){return{enterClass:t+"-enter",enterToClass:t+"-enter-to",enterActiveClass:t+"-enter-active",leaveClass:t+"-leave",leaveToClass:t+"-leave-to",leaveActiveClass:t+"-leave-active"}})),Xr=H&&!K,Hr="transition",zr="animation",Vr="transition",Br="transitionend",Gr="animation",Kr="animationend";Xr&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(Vr="WebkitTransition",Br="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Gr="WebkitAnimation",Kr="webkitAnimationEnd"));var Wr=H?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function qr(t){Wr((function(){Wr(t)}))}function Yr(t,e){var n=t._transitionClasses||(t._transitionClasses=[]);n.indexOf(e)<0&&(n.push(e),Mr(t,e))}function Jr(t,e){t._transitionClasses&&v(t._transitionClasses,e),jr(t,e)}function Zr(t,e,n){var r=ti(t,e),i=r.type,o=r.timeout,s=r.propCount;if(!i)return n();var a=i===Hr?Br:Kr,u=0,c=function(){t.removeEventListener(a,l),n()},l=function(e){e.target===t&&++u>=s&&c()};setTimeout((function(){u0&&(n=Hr,l=s,d=o.length):e===zr?c>0&&(n=zr,l=c,d=u.length):d=(n=(l=Math.max(s,c))>0?s>c?Hr:zr:null)?n===Hr?o.length:u.length:0,{type:n,timeout:l,propCount:d,hasTransform:n===Hr&&Qr.test(r[Vr+"Property"])}}function ei(t,e){for(;t.length1}function ai(t,e){!0!==e.data.show&&ri(e)}var ui=function(t){var n,s,a={},u=t.modules,c=t.nodeOps;for(n=0;nh?g(t,e(i[y+1])?null:i[y+1].elm,i,p,y,o):p>y&&E(n,f,h)}(f,v,y,o,l):r(y)?(r(t.text)&&c.setTextContent(f,""),g(f,null,y,0,y.length-1,o)):r(v)?E(v,0,v.length-1):r(t.text)&&c.setTextContent(f,""):t.text!==n.text&&c.setTextContent(f,n.text),r(h)&&r(p=h.hook)&&r(p=p.postpatch)&&p(t,n)}}}function O(t,e,n){if(i(n)&&r(t.parent))t.parent.data.pendingInsert=e;else for(var o=0;o-1,s.selected!==o&&(s.selected=o);else if(N(pi(s),r))return void(t.selectedIndex!==a&&(t.selectedIndex=a));i||(t.selectedIndex=-1)}}function fi(t,e){return e.every((function(e){return!N(e,t)}))}function pi(t){return"_value"in t?t._value:t.value}function hi(t){t.target.composing=!0}function vi(t){t.target.composing&&(t.target.composing=!1,mi(t.target,"input"))}function mi(t,e){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0),t.dispatchEvent(n)}function yi(t){return!t.componentInstance||t.data&&t.data.transition?t:yi(t.componentInstance._vnode)}var _i={model:ci,show:{bind:function(t,e,n){var r=e.value,i=(n=yi(n)).data&&n.data.transition,o=t.__vOriginalDisplay="none"===t.style.display?"":t.style.display;r&&i?(n.data.show=!0,ri(n,(function(){t.style.display=o}))):t.style.display=r?o:"none"},update:function(t,e,n){var r=e.value;!r!=!e.oldValue&&((n=yi(n)).data&&n.data.transition?(n.data.show=!0,r?ri(n,(function(){t.style.display=t.__vOriginalDisplay})):ii(n,(function(){t.style.display="none"}))):t.style.display=r?t.__vOriginalDisplay:"none")},unbind:function(t,e,n,r,i){i||(t.style.display=t.__vOriginalDisplay)}}},gi={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function bi(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?bi(Ve(e.children)):t}function Ei(t){var e={},n=t.$options;for(var r in n.propsData)e[r]=t[r];var i=n._parentListeners;for(var o in i)e[b(o)]=i[o];return e}function xi(t,e){if(/\d-keep-alive$/.test(e.tag))return t("keep-alive",{props:e.componentOptions.propsData})}var wi=function(t){return t.tag||ze(t)},Ti=function(t){return"show"===t.name},Oi={name:"transition",props:gi,abstract:!0,render:function(t){var e=this,n=this.$slots.default;if(n&&(n=n.filter(wi)).length){var r=this.mode,i=n[0];if(function(t){for(;t=t.parent;)if(t.data.transition)return!0}(this.$vnode))return i;var s=bi(i);if(!s)return i;if(this._leaving)return xi(t,i);var a="__transition-"+this._uid+"-";s.key=null==s.key?s.isComment?a+"comment":a+s.tag:o(s.key)?0===String(s.key).indexOf(a)?s.key:a+s.key:s.key;var u=(s.data||(s.data={})).transition=Ei(this),c=this._vnode,l=bi(c);if(s.data.directives&&s.data.directives.some(Ti)&&(s.data.show=!0),l&&l.data&&!function(t,e){return e.key===t.key&&e.tag===t.tag}(s,l)&&!ze(l)&&(!l.componentInstance||!l.componentInstance._vnode.isComment)){var d=l.data.transition=C({},u);if("out-in"===r)return this._leaving=!0,oe(d,"afterLeave",(function(){e._leaving=!1,e.$forceUpdate()})),xi(t,i);if("in-out"===r){if(ze(s))return c;var f,p=function(){f()};oe(u,"afterEnter",p),oe(u,"enterCancelled",p),oe(d,"delayLeave",(function(t){f=t}))}}return i}}},Ci=C({tag:String,moveClass:String},gi);function Si(t){t.elm._moveCb&&t.elm._moveCb(),t.elm._enterCb&&t.elm._enterCb()}function ki(t){t.data.newPos=t.elm.getBoundingClientRect()}function Ai(t){var e=t.data.pos,n=t.data.newPos,r=e.left-n.left,i=e.top-n.top;if(r||i){t.data.moved=!0;var o=t.elm.style;o.transform=o.WebkitTransform="translate("+r+"px,"+i+"px)",o.transitionDuration="0s"}}delete Ci.mode;var $i={Transition:Oi,TransitionGroup:{props:Ci,beforeMount:function(){var t=this,e=this._update;this._update=function(n,r){var i=Ye(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,i(),e.call(t,n,r)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,i=this.$slots.default||[],o=this.children=[],s=Ei(this),a=0;a-1?Gn[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:Gn[t]=/HTMLUnknownElement/.test(e.toString())},C(wn.options.directives,_i),C(wn.options.components,$i),wn.prototype.__patch__=H?ui:k,wn.prototype.$mount=function(t,e){return function(t,e,n){var r;return t.$el=e,t.$options.render||(t.$options.render=pt),tn(t,"beforeMount"),r=function(){t._update(t._render(),n)},new pn(t,r,k,{before:function(){t._isMounted&&!t._isDestroyed&&tn(t,"beforeUpdate")}},!0),n=!1,null==t.$vnode&&(t._isMounted=!0,tn(t,"mounted")),t}(this,t=t&&H?function(t){return"string"==typeof t?document.querySelector(t)||document.createElement("div"):t}(t):void 0,e)},H&&setTimeout((function(){M.devtools&&et&&et.emit("init",wn)}),0);const Ni=wn;var Li=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"menuv",class:[{hide:!t.show||!t.menu},t.position,t.size,t.theme],attrs:{id:"menuv","data-uuid":t.uuid}},[n("v-style",[t._v("\n html,\n body {\n color: "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b))+";\n }\n\n .menuv."+t._s(t.theme)+' .menuv-header {\n background: url("https://nui-img/'+t._s(t.dictionary)+"/"+t._s(t.texture)+'") no-repeat;\n background-size: 100%;\n }\n\n .menuv.'+t._s(t.theme)+" .menuv-header .menuv-bg-icon i,\n .menuv."+t._s(t.theme)+" .menuv-header .menuv-bg-icon svg {\n color: rgb("+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+");\n }\n\n .menuv."+t._s(t.theme)+" .menuv-subheader {\n background-color: rgb("+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+");\n }\n\n .menuv."+t._s(t.theme)+" .menuv-items .menuv-item.active {\n border-left: 0.5em solid rgb("+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+");\n border-right: 0.5em solid rgb("+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+");\n background-color: rgb("+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+");\n color: "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b))+";\n }\n\n .menuv."+t._s(t.theme)+" .menuv-items .menuv-item.active i,\n .menuv."+t._s(t.theme)+" .menuv-items .menuv-item.active svg {\n color: "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b))+";\n }\n\n .menuv."+t._s(t.theme)+" .menuv-items .menuv-item.active span.menuv-icon {\n border-right: 1px solid "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b))+";\n }\n\n .menuv."+t._s(t.theme)+" .menuv-items span.menuv-options span.menuv-btn {\n color: "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b))+";\n }\n\n .menuv."+t._s(t.theme)+" .menuv-items span.menuv-options span.menuv-btn.active {\n background-color: rgb("+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+");\n color: "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b))+";\n }\n\n .menuv."+t._s(t.theme)+" .menuv-items .menuv-item.active span.menuv-options span.menuv-btn {\n background-color: rgb("+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+");\n color: "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b))+";\n }\n\n .menuv."+t._s(t.theme)+" .menuv-items .menuv-item.active span.menuv-options span.menuv-btn.active {\n background-color: black;\n color: white;\n }\n\n .menuv."+t._s(t.theme)+' .menuv-items input[type="range"]::-webkit-slider-runnable-track {\n background: rgba('+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+", 0.50);\n box-shadow: 0px 0px 0px "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b,.5))+";\n border: 0px solid "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b,.5))+";\n }\n\n .menuv."+t._s(t.theme)+' .menuv-items input[type="range"]::-webkit-slider-thumb {\n border: 1px solid rgb('+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+");\n background: rgb("+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+");\n box-shadow: 0px 0px 0px "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b,.5))+";\n }\n\n .menuv."+t._s(t.theme)+' .menuv-items .menuv-item.active input[type="range"]::-webkit-slider-thumb {\n background: '+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b))+" !important;\n border: 1px solid "+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b,.5))+" !important;\n }\n\n .menuv."+t._s(t.theme)+' .menuv-items .menuv-item.active input[type="range"]::-webkit-slider-runnable-track,\n .menuv.'+t._s(t.theme)+' .menuv-items .menuv-item.active input[type="range"]:focus::-webkit-slider-runnable-track {\n background: '+t._s(t.TEXT_COLOR(t.color.r,t.color.g,t.color.b,.5))+" !important;\n }\n\n .menuv."+t._s(t.theme)+' .menuv-items input[type="range"]:focus::-webkit-slider-runnable-track {\n background: rgba('+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+", 0.50);\n }\n\n .menuv."+t._s(t.theme)+" .menuv-items .menuv-desc {\n border-left: 0.375em solid rgb("+t._s(t.color.r)+","+t._s(t.color.g)+","+t._s(t.color.b)+");\n }\n ")]),t._v(" "),n("header",{staticClass:"menuv-header"},[n("strong",{domProps:{innerHTML:t._s(t.FORMAT_TEXT(t.title))}})]),t._v(" "),n("nav",{staticClass:"menuv-subheader",domProps:{innerHTML:t._s(t.FORMAT_TEXT(t.subtitle))}}),t._v(" "),n("ul",{ref:"items",staticClass:"menuv-items"},t._l(t.items,(function(e){return n("li",{key:e.uuid,staticClass:"menuv-item media",class:[{active:t.index+1==e.index,hasIcon:"none"!=t.ENSURE(e.icon,"none"),disabled:e.disabled},"menuv-"+e.type],attrs:{index:e.index-1}},["none"!=t.ENSURE(e.icon,"none")?n("div",{staticClass:"media-left item-icon"},[n("span",{staticClass:"menuv-icon"},[t._v(t._s(t.ENSURE(e.icon,"none")))])]):t._e(),t._v(" "),n("div",{staticClass:"media-content flex-left item-title",domProps:{innerHTML:t._s(t.FORMAT_TEXT(e.label))}}),t._v(" "),n("div",{staticClass:"media-right"},["menu"==e.type?n("i",{staticClass:"fas fa-arrow-right"}):t._e(),t._v(" "),"checkbox"==e.type?n("i",{class:{"fas fa-check":e.value,"far fa-square":!e.value}}):t._e(),t._v(" "),"range"==e.type?n("input",{attrs:{type:"range",min:e.min,max:e.max},domProps:{value:e.value}}):t._e(),t._v(" "),"confirm"==e.type?n("span",{staticClass:"menuv-options"},[n("span",{staticClass:"menuv-btn",class:{active:e.value}},[t._v("YES")]),t._v(" "),n("span",{staticClass:"menuv-btn",class:{active:!e.value}},[t._v("NO")])]):t._e(),t._v(" "),"label"==e.type?n("span",{staticClass:"menuv-label",domProps:{innerHTML:t._s(t.FORMAT_TEXT(e.value))}}):t._e(),t._v(" "),"slider"==e.type?n("span",{staticClass:"menuv-options"},[n("i",{staticClass:"fas fa-chevron-left"}),t._v(" "),n("span",{domProps:{innerHTML:t._s(t.GET_SLIDER_LABEL({uuid:e.uuid}))}}),t._v(" "),n("i",{staticClass:"fas fa-chevron-right"})]):t._e()])])})),0),t._v(" "),n("footer",{staticClass:"menuv-description",class:{hide:t.IS_DEFAULT(t.GET_CURRENT_DESCRIPTION())}},[n("strong",{domProps:{innerHTML:t._s(t.GET_CURRENT_DESCRIPTION())}})])],1)};Li._withStripped=!0;const Pi=Ni.component("v-style",{render:function(t){return t("style",this.$slots.default)}});var Ri=n(81),Ii=n.n(Ri);Ni.use(Ii(),{container:"ul.menuv-items",duration:500,easing:"ease-in",offset:-25,force:!0,cancelable:!1,onStart:!1,onDone:!1,onCancel:!1,x:!1,y:!0});var Di=function(t,e,n,r,i,o,s,a){var u,c="function"==typeof t?t.options:t;if(e&&(c.render=e,c.staticRenderFns=[],c._compiled=!0),u)if(c.functional){c._injectStyles=u;var l=c.render;c.render=function(t,e){return u.call(e),l(t,e)}}else{var d=c.beforeCreate;c.beforeCreate=d?[].concat(d,u):[u]}return{exports:t,options:c}}(Ni.extend({template:"#menuv_template",name:"menuv",components:{STYLE:Pi},data:()=>({theme:"default",resource:"menuv",uuid:"",menu:!1,show:!1,title:"MenuV",subtitle:"",position:"topleft",size:"size-110",texture:"none",dictionary:"none",color:{r:0,g:0,b:255},items:[],listener:t=>{},index:0,sounds:{},cached_indexes:{}}),destroyed(){window.removeEventListener("message",this.listener)},mounted(){this.listener=t=>{const e=t.data||t.detail;if(!e||!e.action)return;const n=e.action;this[n]&&this[n](e)},window.addEventListener("message",this.listener),this.POST("https://menuv/loaded",{})},watch:{theme(){},title(){},subtitle(){},position(){},color(){},options(){},menu(){},show(){},size(){},index(t,e){let n=null,r=null;e>=0&&e=0&&t=t.length||this.index<0)return;let n=null;const r=t[this.index];if(null!=r){for(var i=0;it.index>e.index?1:t.indext.index>e.index?1:t.index=i.length?i.push(t):i.splice(e,0,t),this.items=i.sort(((t,e)=>t.index>e.index?1:t.indext.index>e.index?1:t.index=n.length?this.FORMAT_TEXT(n[0].label||"Unknown"):this.FORMAT_TEXT(n[t].label||"Unknown")}return""},GET_CURRENT_DESCRIPTION(){const t=this.index||0;return t>=0&&t1&&(r=1),r<0&&(r=0),(.299*t+.587*e+.114*n)/255>.5?`rgba(0, 0, 0, ${r})`:`rgba(255, 255, 255, ${r})`},IS_DEFAULT:function(t){return"string"==typeof t?null==t||"nil"==t||""==t:("number"==typeof t||"boolean"==typeof t)&&0==t},KEY_PRESSED({key:t}){if(!this.menu||!this.show)return;if(void 0===t||null==t)return;const e=`KEY_${t}`;this[e]&&this[e]()},RESOURCE_STOPPED({resource:t}){this.menu&&this.resource==t&&this.RESET_MENU()},KEY_UP:function(){const t=this.PREV_INDEX(this.index);this.index!=t&&(this.index=t,this.sounds.UP&&"native"==this.sounds.UP.type&&this.POST("https://menuv/sound",{key:"UP"}))},KEY_DOWN:function(){const t=this.NEXT_INDEX(this.index);this.index!=t&&(this.index=t,this.sounds.DOWN&&"native"==this.sounds.DOWN.type&&this.POST("https://menuv/sound",{key:"DOWN"}))},KEY_LEFT:function(){if(this.index<0||this.items.length<=this.index||this.items[this.index].disabled)return;const t=this.items[this.index];if("button"!=t.type&&"menu"!=t.type&&"label"!=t.type&&"unknown"!=t.type)switch(t.type){case"confirm":case"checkbox":const e=t.value;this.items[this.index].value=!e,this.sounds.LEFT&&"native"==this.sounds.LEFT.type&&this.POST("https://menuv/sound",{key:"LEFT"});break;case"range":let n=null,r=t.value;n=r-1<=t.min?t.min:r-1>=t.max?t.max:this.items[this.index].value-1,n!=this.items[this.index].value&&(this.items[this.index].value=n,this.sounds.LEFT&&"native"==this.sounds.LEFT.type&&this.POST("https://menuv/sound",{key:"LEFT"}));break;case"slider":let i=null;const o=t.value,s=t.values||[];if(s.length<=0)return;i=o-1<0||o-1>=s.length?s.length-1:this.items[this.index].value-1,i!=this.items[this.index].value&&(this.items[this.index].value=i,this.sounds.LEFT&&"native"==this.sounds.LEFT.type&&this.POST("https://menuv/sound",{key:"LEFT"}))}},KEY_RIGHT:function(){if(this.index<0||this.items.length<=this.index||this.items[this.index].disabled)return;const t=this.items[this.index];if("button"!=t.type&&"menu"!=t.type&&"label"!=t.type&&"unknown"!=t.type)switch(t.type){case"confirm":case"checkbox":const e=t.value;this.items[this.index].value=!e,this.sounds.RIGHT&&"native"==this.sounds.RIGHT.type&&this.POST("https://menuv/sound",{key:"RIGHT"});break;case"range":let n=null,r=t.value;n=r+1<=t.min?t.min:r+1>=t.max?t.max:this.items[this.index].value+1,n!=this.items[this.index].value&&(this.items[this.index].value=n,this.sounds.RIGHT&&"native"==this.sounds.RIGHT.type&&this.POST("https://menuv/sound",{key:"RIGHT"}));break;case"slider":let i=null;const o=t.value,s=t.values||[];if(s.length<=0)return;i=o+1<0||o+1>=s.length?0:this.items[this.index].value+1,i!=this.items[this.index].value&&(this.items[this.index].value=i,this.sounds.RIGHT&&"native"==this.sounds.RIGHT.type&&this.POST("https://menuv/sound",{key:"RIGHT"}))}},KEY_ENTER:function(){if(this.index<0||this.items.length<=this.index||this.items[this.index].disabled)return;this.sounds.ENTER&&"native"==this.sounds.ENTER.type&&this.POST("https://menuv/sound",{key:"ENTER"});const t=this.items[this.index];switch(t.type){case"button":case"menu":this.POST("https://menuv/submit",{uuid:t.uuid,value:null,r:this.resource});break;case"confirm":this.POST("https://menuv/submit",{uuid:t.uuid,value:t.value,r:this.resource});break;case"range":let e=t.value;e<=t.min?e=t.min:e>=t.max&&(e=t.max),this.POST("https://menuv/submit",{uuid:t.uuid,value:e,r:this.resource});break;case"checkbox":const n=t.value;this.items[this.index].value=!n,this.POST("https://menuv/submit",{uuid:t.uuid,value:this.items[this.index].value,r:this.resource});break;case"slider":let r=t.value;const i=t.values||[];if(i.length<=0||r<0||r>=i.length)return;this.POST("https://menuv/submit",{uuid:t.uuid,value:r,r:this.resource})}},KEY_CLOSE:function(){this.sounds.CLOSE&&"native"==this.sounds.CLOSE.type&&this.POST("https://menuv/sound",{key:"CLOSE"}),this.POST("https://menuv/close",{uuid:this.uuid,r:this.resource}),this.CLOSE_MENU({uuid:this.uuid})},KEY_CLOSE_ALL:function(){this.sounds.CLOSE&&"native"==this.sounds.CLOSE.type&&this.POST("https://menuv/sound",{key:"CLOSE"}),this.POST("https://menuv/close_all",{r:this.resource}),this.RESET_MENU()},POST:function(t,e){var n=new XMLHttpRequest;n.open("POST",t,!0),n.open("POST",t,!0),n.setRequestHeader("Content-Type","application/json; charset=UTF-8"),n.send(JSON.stringify(e))},NEXT_INDEX:function(t){null!=t&&void 0!==t||(t=this.index);let e=0,n=-2;if(this.items.length<=0)return-1;for(;n<-1;)if(t+1+e=this.items.length)return-1;{const r=t+1+e-this.items.length;r=0)this.items[t-1-e].disabled?e++:n=t-1-e;else{if(e>=this.items.length)return-1;{const r=t-1-e+this.items.length;r=0?this.items[r].disabled?e++:n=r:e++}}return n<0?-1:n},NL2BR:function(t,e,n){var r=n?"
":"
";return(t+"").replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g,e?"$1"+r:"$1"+r+"$2")},FORMAT_TEXT:function(t){return t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=this.ENSURE(t,"")).replace(/\^0/g,'')).replace(/\^1/g,'')).replace(/\^2/g,'')).replace(/\^3/g,'')).replace(/\^4/g,'')).replace(/\^5/g,'')).replace(/\^6/g,'')).replace(/\^7/g,'')).replace(/\^8/g,'')).replace(/\^9/g,'')).replace(/~r~/g,'')).replace(/~g~/g,'')).replace(/~b~/g,'')).replace(/~y~/g,'')).replace(/~p~/g,'')).replace(/~c~/g,'')).replace(/~m~/g,'')).replace(/~u~/g,'')).replace(/~o~/g,'')).replace(/~n~/g,"
")).replace(/~s~/g,'')).replace(/~h~/g,""),(new DOMParser).parseFromString(t||"","text/html").body.innerHTML}}}),Li);Di.options.__file="source/app/vue_templates/menuv.vue";const Mi=Di.exports;new Ni({el:"#menuv",render:t=>t(Mi)})})()})(); \ No newline at end of file diff --git a/resources/[standalone]/menuv/dist/assets/js/menuv.js.LICENSE.txt b/resources/[standalone]/menuv/dist/assets/js/menuv.js.LICENSE.txt new file mode 100644 index 0000000..f9b3aa0 --- /dev/null +++ b/resources/[standalone]/menuv/dist/assets/js/menuv.js.LICENSE.txt @@ -0,0 +1,11 @@ +/*! + * vue-scrollto v2.20.0 + * (c) 2019 Randjelovic Igor + * @license MIT + */ + +/*! + * Vue.js v2.6.12 + * (c) 2014-2020 Evan You + * Released under the MIT License. + */ diff --git a/resources/[standalone]/menuv/dist/menuv.html b/resources/[standalone]/menuv/dist/menuv.html new file mode 100644 index 0000000..ac65934 --- /dev/null +++ b/resources/[standalone]/menuv/dist/menuv.html @@ -0,0 +1 @@ +MenuV \ No newline at end of file diff --git a/resources/[standalone]/menuv/fxmanifest.lua b/resources/[standalone]/menuv/fxmanifest.lua new file mode 100644 index 0000000..8063b95 --- /dev/null +++ b/resources/[standalone]/menuv/fxmanifest.lua @@ -0,0 +1,37 @@ +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +fx_version 'cerulean' +game 'gta5' +lua54 'yes' + +name 'MenuV' +version '1.4.1' +description 'FiveM menu library for creating menu\'s' +author 'ThymonA' +contact 'contact@arens.io' +url 'https://github.com/ThymonA/menuv/' + +files { + 'menuv.lua', + 'menuv/components/*.lua', + 'dist/*.html', + 'dist/assets/css/*.css', + 'dist/assets/js/*.js', + 'dist/assets/fonts/*.woff', + 'languages/*.json' +} + +ui_page 'dist/menuv.html' + +client_scripts { + 'config.lua', + 'menuv/components/utilities.lua', + 'menuv/menuv.lua' +} \ No newline at end of file diff --git a/resources/[standalone]/menuv/languages/en.json b/resources/[standalone]/menuv/languages/en.json new file mode 100644 index 0000000..99dbcad --- /dev/null +++ b/resources/[standalone]/menuv/languages/en.json @@ -0,0 +1,22 @@ +{ + "language": "en", + "translators": [ + { + "name": "ThymonA", + "emails": ["contact@thymonarens.nl"], + "github": "https://github.com/ThymonA" + } + ], + "translations": { + "keyboard": "Keyboard", + "controller": "Controller", + "keybind_key_up": "Up in a menu", + "keybind_key_down": "Down in a menu", + "keybind_key_left": "Left in a menu", + "keybind_key_right": "Right in a menu", + "keybind_key_enter": "Confirm item in menu", + "keybind_key_close": "Closing a menu", + "keybind_key_close_all": "Close all menus (including parents)", + "open_menu": "Open menu '%s' with this key" + } +} \ No newline at end of file diff --git a/resources/[standalone]/menuv/languages/fr.json b/resources/[standalone]/menuv/languages/fr.json new file mode 100644 index 0000000..1a91fb5 --- /dev/null +++ b/resources/[standalone]/menuv/languages/fr.json @@ -0,0 +1,22 @@ +{ + "language": "fr", + "translators": [ + { + "name": "Korioz", + "emails": ["pro.korioz@protonmail.com"], + "github": "https://github.com/Korioz" + } + ], + "translations": { + "keyboard": "Clavier", + "controller": "Manette", + "keybind_key_up": "Haut dans un menu", + "keybind_key_down": "Bas dans un menu", + "keybind_key_left": "Gauche dans un menu", + "keybind_key_right": "Droite dans un menu", + "keybind_key_enter": "Confirmer dans un menu", + "keybind_key_close": "Fermer un menu", + "keybind_key_close_all": "Fermer tout les menus", + "open_menu": "Ouvrir menu '%s'" + } +} \ No newline at end of file diff --git a/resources/[standalone]/menuv/languages/nl.json b/resources/[standalone]/menuv/languages/nl.json new file mode 100644 index 0000000..9b8333b --- /dev/null +++ b/resources/[standalone]/menuv/languages/nl.json @@ -0,0 +1,22 @@ +{ + "language": "nl", + "translators": [ + { + "name": "ThymonA", + "emails": ["contact@thymonarens.nl"], + "github": "https://github.com/ThymonA" + } + ], + "translations": { + "keyboard": "Toetsenbord", + "controller": "Controller", + "keybind_key_up": "Omhoog in een menu", + "keybind_key_down": "Omlaag in een menu", + "keybind_key_left": "Links in een menu", + "keybind_key_right": "Rechts in een menu", + "keybind_key_enter": "Item bevestigen in menu", + "keybind_key_close": "Sluiten van een menu", + "keybind_key_close_all": "Sluit alle menu's (inclusief ouders)", + "open_menu": "Menu '%s' openen met deze key" + } +} \ No newline at end of file diff --git a/resources/[standalone]/menuv/menuv.lua b/resources/[standalone]/menuv/menuv.lua new file mode 100644 index 0000000..25799c8 --- /dev/null +++ b/resources/[standalone]/menuv/menuv.lua @@ -0,0 +1,2486 @@ +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +local assert = assert +local pairs = assert(pairs) +local rawget = assert(rawget) +local rawset = assert(rawset) +local insert = assert(table.insert) +local remove = assert(table.remove) +local format = assert(string.format) +local upper = assert(string.upper) +local lower = assert(string.lower) +local setmetatable = assert(setmetatable) + +--- FiveM globals +local GET_CURRENT_RESOURCE_NAME = assert(GetCurrentResourceName) +local HAS_STREAMED_TEXTURE_DICT_LOADED = assert(HasStreamedTextureDictLoaded) +local REQUEST_STREAMED_TEXTURE_DICT = assert(RequestStreamedTextureDict) +local REGISTER_KEY_MAPPING = assert(RegisterKeyMapping) +local REGISTER_COMMAND = assert(RegisterCommand) +local GET_HASH_KEY = assert(GetHashKey) +local CreateThread = assert(Citizen.CreateThread) +local Wait = assert(Citizen.Wait) + +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +Config = { + Language = 'en', + HideInterval = 250, + Sounds = { + UP = { + type = 'native', + name = 'NAV_UP_DOWN', + library = 'HUD_FREEMODE_SOUNDSET' + }, + DOWN = { + type = 'native', + name = 'NAV_UP_DOWN', + library = 'HUD_FREEMODE_SOUNDSET' + }, + LEFT = { + type = 'native', + name = 'NAV_LEFT_RIGHT', + library = 'HUD_FRONTEND_DEFAULT_SOUNDSET' + }, + RIGHT = { + type = 'native', + name = 'NAV_LEFT_RIGHT', + library = 'HUD_FRONTEND_DEFAULT_SOUNDSET' + }, + ENTER = { + type = 'native', + name = 'SELECT', + library = 'HUD_FRONTEND_DEFAULT_SOUNDSET' + }, + CLOSE = { + type = 'native', + name = 'BACK', + library = 'HUD_FRONTEND_DEFAULT_SOUNDSET' + } + } +} + +_G.Config = Config +_ENV.Config = Config +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +local assert = assert +local type = assert(type) +local tonumber = assert(tonumber) +local tostring = assert(tostring) +local lower = assert(string.lower) +local upper = assert(string.upper) +local sub = assert(string.sub) +local encode = assert(json.encode) +local decode = assert(json.decode) +local floor = assert(math.floor) +local random = assert(math.random) +local randomseed = assert(math.randomseed) +local rawget = assert(rawget) +local setmetatable = assert(setmetatable) + +--- FiveM globals +local GET_GAME_TIMER = assert(GetGameTimer) +local GET_CURRENT_RESOURCE_NAME = assert(GetCurrentResourceName) + +--- Utilities for MenuV +---@class Utilities +local Utilities = setmetatable({ __class = 'Utilities' }, {}) + +--- Returns `true` if `input` starts with `start`, otherwise `false` +---@param input string Checks if this string starts with `start` +---@param start string Checks if `input` starts with this +---@return boolean `true` if `input` starts with `start`, otherwise `false` +function Utilities:StartsWith(input, start) + if (self:Typeof(input) ~= 'string') then return false end + if (self:Typeof(start) == 'number') then start = tostring(start) end + if (self:Typeof(start) ~= 'string') then return false end + + return sub(input, 1, #start) == start +end + +--- Returns `true` if `input` ends with `ends`, otherwise `false` +---@param input string Checks if this string ends with `ends` +---@param ends string Checks if `input` ends with this +---@return boolean `true` if `input` ends with `ends`, otherwise `false` +function Utilities:EndsWith(input, ends) + if (self:Typeof(input) ~= 'string') then return false end + if (self:Typeof(ends) == 'number') then ends = tostring(ends) end + if (self:Typeof(ends) ~= 'string') then return false end + + return sub(input, -#ends) == ends +end + +--- Returns the type of given `input` +---@param input any Any input +---@return string Type of given input +function Utilities:Typeof(input) + if (input == nil) then return 'nil' end + + local rawType = type(input) or 'nil' + + if (rawType ~= 'table') then return rawType end + + local isFXFunction = rawget(input, '__cfx_functionReference') ~= nil or + rawget(input, '__cfx_async_retval') ~= nil + + if (isFXFunction) then return 'function' end + if (rawget(input, '__cfx_functionSource') ~= nil) then return 'number' end + + local rawClass = rawget(input, '__class') + + if (rawClass ~= nil) then return type(rawClass) == 'string' and rawClass or 'class' end + + local rawTableType = rawget(input, '__type') + + if (rawTableType ~= nil) then return type(rawTableType) == 'string' and rawTableType or 'table' end + + return rawType +end + +local INPUT_GROUPS = { + [0] = "KEYBOARD", + [2] = "CONTROLLER" +} + +local INPUT_TYPE_GROUPS = { + ["KEYBOARD"] = 0, + ["MOUSE_ABSOLUTEAXIS"] = 0, + ["MOUSE_CENTEREDAXIS"] = 0, + ["MOUSE_RELATIVEAXIS"] = 0, + ["MOUSE_SCALEDAXIS"] = 0, + ["MOUSE_NORMALIZED"] = 0, + ["MOUSE_WHEEL"] = 0, + ["MOUSE_BUTTON"] = 0, + ["MOUSE_BUTTONANY"] = 0, + ["MKB_AXIS"] = 0, + ["PAD_AXIS"] = 2, + ["PAD_DIGITALBUTTON"] = 2, + ["PAD_DIGITALBUTTONANY"] = 2, + ["PAD_ANALOGBUTTON"] = 2, + ["JOYSTICK_POV"] = 2, + ["JOYSTICK_POV_AXIS"] = 2, + ["JOYSTICK_BUTTON"] = 2, + ["JOYSTICK_AXIS"] = 2, + ["JOYSTICK_IAXIS"] = 2, + ["JOYSTICK_AXIS_NEGATIVE"] = 2, + ["JOYSTICK_AXIS_POSITIVE"] = 2, + ["PAD_DEBUGBUTTON"] = 2, + ["GAME_CONTROLLED"] = 2, + ["DIGITALBUTTON_AXIS"] = 2, +} + +function Utilities:GetInputTypeGroup(inputType) + return INPUT_TYPE_GROUPS[inputType] or 0 +end + +function Utilities:GetInputGroupName(inputTypeGroup) + return INPUT_GROUPS[inputTypeGroup] or "KEYBOARD" +end + +--- Transform any `input` to the same type as `defaultValue` +---@type function +---@param input any Transform this `input` to `defaultValue`'s type +---@param defaultValue any Returns this if `input` can't transformed to this type +---@param ignoreDefault boolean Don't return default value if this is true +---@return any Returns `input` matches the `defaultValue` type or `defaultValue` +function Utilities:Ensure(input, defaultValue, ignoreDefault) + ignoreDefault = type(ignoreDefault) == 'boolean' and ignoreDefault or false + + if (defaultValue == nil) then return nil end + + local requiredType = self:Typeof(defaultValue) + + if (requiredType == 'nil') then return nil end + + local inputType = self:Typeof(input) + + if (inputType == requiredType) then return input end + if (inputType == 'nil') then return defaultValue end + + if (requiredType == 'number') then + if (inputType == 'boolean') then return input and 1 or 0 end + + return tonumber(input) or (not ignoreDefault and defaultValue or nil) + end + + if (requiredType == 'string') then + if (inputType == 'boolean') then return input and 'yes' or 'no' end + if (inputType == 'vector3') then return encode({ x = input.x, y = input.y, z = input.z }) or (not ignoreDefault and defaultValue or nil) end + if (inputType == 'vector2') then return encode({ x = input.x, y = input.y }) or (not ignoreDefault and defaultValue or nil) end + if (inputType == 'table') then return encode(input) or (not ignoreDefault and defaultValue or nil) end + + local result = tostring(input) + + if (result == 'nil') then + return not ignoreDefault and defaultValue or 'nil' + end + + return result + end + + if (requiredType == 'boolean') then + if (inputType == 'string') then + input = lower(input) + + if (input == 'true' or input == '1' or input == 'yes' or input == 'y') then return true end + if (input == 'false' or input == '0' or input == 'no' or input == 'n') then return false end + + return (not ignoreDefault and defaultValue or nil) + end + + if (inputType == 'number') then + if (input == 1) then return true end + if (input == 0) then return false end + + return (not ignoreDefault and defaultValue or nil) + end + + return (not ignoreDefault and defaultValue or nil) + end + + if (requiredType == 'table') then + if (inputType == 'string') then + if (self:StartsWith(input, '{') and self:EndsWith(input, '}')) then + return decode(input) or (not ignoreDefault and defaultValue or nil) + end + + if (self:StartsWith(input, '[') and self:EndsWith(input, ']')) then + return decode(input) or (not ignoreDefault and defaultValue or nil) + end + + return (not ignoreDefault and defaultValue or nil) + end + + if (inputType == 'vector3') then return { x = input.x or 0, y = input.y or 0, z = input.z or 0 } end + if (inputType == 'vector2') then return { x = input.x or 0, y = input.y or 0 } end + + return (not ignoreDefault and defaultValue or nil) + end + + if (requiredType == 'vector3') then + if (inputType == 'table') then + local _x = self:Ensure(input.x, defaultValue.x) + local _y = self:Ensure(input.y, defaultValue.y) + local _z = self:Ensure(input.z, defaultValue.z) + + return vector3(_x, _y, _z) + end + + if (inputType == 'vector2') then + local _x = self:Ensure(input.x, defaultValue.x) + local _y = self:Ensure(input.y, defaultValue.y) + + return vector3(_x, _y, 0) + end + + if (inputType == 'number') then + return vector3(input, input, input) + end + + return (not ignoreDefault and defaultValue or nil) + end + + if (requiredType == 'vector2') then + if (inputType == 'table') then + local _x = self:Ensure(input.x, defaultValue.x) + local _y = self:Ensure(input.y, defaultValue.y) + + return vector2(_x, _y) + end + + if (inputType == 'vector3') then + local _x = self:Ensure(input.x, defaultValue.x) + local _y = self:Ensure(input.y, defaultValue.y) + + return vector2(_x, _y) + end + + if (inputType == 'number') then + return vector2(input, input) + end + + return (not ignoreDefault and defaultValue or nil) + end + + return (not ignoreDefault and defaultValue or nil) +end + +--- Checks if input exists in inputs +--- '0' and 0 are both the same '0' == 0 equals `true` +--- 'yes' and true are both the same 'yes' == true equals `true` +---@param input any Any input +---@param inputs any[] Any table +---@param checkType string | "'value'" | "'key'" | "'both'" +---@return boolean Returns `true` if input has been found as `key` and/or `value` +function Utilities:Any(input, inputs, checkType) + if (input == nil) then return false end + if (inputs == nil) then return false end + + inputs = self:Ensure(inputs, {}) + checkType = lower(self:Ensure(checkType, 'value')) + + local checkMethod = 1 + + if (checkType == 'value' or checkType == 'v') then + checkMethod = 1 + elseif (checkType == 'key' or checkType == 'k') then + checkMethod = -1 + elseif (checkType == 'both' or checkType == 'b') then + checkMethod = 0 + end + + for k, v in pairs(inputs) do + if (checkMethod == 0 or checkMethod == -1) then + local checkK = self:Ensure(input, k, true) + + if (checkK ~= nil and checkK == k) then return true end + end + + if (checkMethod == 0 or checkMethod == 1) then + local checkV = self:Ensure(input, v, true) + + if (checkV ~= nil and checkV == v) then return true end + end + end + + return false +end + +--- Round any `value` +---@param value number Round this value +---@param decimal number Number of decimals +---@return number Rounded number +function Utilities:Round(value, decimal) + value = self:Ensure(value, 0) + decimal = self:Ensure(decimal, 0) + + if (decimal > 0) then + return floor((value * 10 ^ decimal) + 0.5) / (10 ^ decimal) + end + + return floor(value + 0.5) +end + +--- Checks if `item1` equals `item2` +---@param item1 any Item1 +---@param item2 any Item2 +---@return boolean `true` if both are equal, otherwise `false` +function Utilities:Equal(item1, item2) + if (item1 == nil and item2 == nil) then return true end + if (item1 == nil or item2 == nil) then return false end + + if (type(item1) == 'table') then + local item1EQ = rawget(item1, '__eq') + + if (item1EQ ~= nil and self:Typeof(item1EQ) == 'function') then + return item1EQ(item1, item2) + end + + return item1 == item2 + end + + if (type(item2) == 'table') then + local item2EQ = rawget(item2, '__eq') + + if (item2EQ ~= nil and self:Typeof(item2EQ) == 'function') then + return item2EQ(item2, item1) + end + + return item2 == item1 + end + + return item1 == item2 +end + +local function tohex(x) + x = Utilities:Ensure(x, 32) + + local s, base, d = '', 16 + + while x > 0 do + d = x % base + 1 + x = floor(x / base) + s = sub('0123456789abcdef', d, d) .. s + end + + while #s < 2 do s = ('0%s'):format(s) end + + return s +end + +local function bitwise(x, y, matrix) + x = Utilities:Ensure(x, 32) + y = Utilities:Ensure(y, 16) + matrix = Utilities:Ensure(matrix, {{0,0}, {0, 1}}) + + local z, pow = 0, 1 + + while x > 0 or y > 0 do + z = z + (matrix[x %2 + 1][y %2 + 1] * pow) + pow = pow * 2 + x = floor(x / 2) + y = floor(y / 2) + end + + return z +end + +--- Generates a random UUID like: 00000000-0000-0000-0000-000000000000 +---@return string Random generated UUID +function Utilities:UUID() + randomseed(GET_GAME_TIMER() + random(30720, 92160)) + + ---@type number[] + local bytes = { + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255) + } + + bytes[7] = bitwise(bytes[7], 0x0f, {{0,0},{0,1}}) + bytes[7] = bitwise(bytes[7], 0x40, {{0,1},{1,1}}) + bytes[9] = bitwise(bytes[7], 0x3f, {{0,0},{0,1}}) + bytes[9] = bitwise(bytes[7], 0x80, {{0,1},{1,1}}) + + return upper(('%s%s%s%s-%s%s-%s%s-%s%s-%s%s%s%s%s%s'):format( + tohex(bytes[1]), tohex(bytes[2]), tohex(bytes[3]), tohex(bytes[4]), + tohex(bytes[5]), tohex(bytes[6]), + tohex(bytes[7]), tohex(bytes[8]), + tohex(bytes[9]), tohex(bytes[10]), + tohex(bytes[11]), tohex(bytes[12]), tohex(bytes[13]), tohex(bytes[14]), tohex(bytes[15]), tohex(bytes[16]) + )) +end + +--- Replace a string that contains `this` to `that` +---@param str string String where to replace in +---@param this string Word that's need to be replaced +---@param that string Replace `this` whit given string +---@return string String where `this` has been replaced with `that` +function Utilities:Replace(str, this, that) + local b, e = str:find(this, 1, true) + + if b == nil then + return str + else + return str:sub(1, b - 1) .. that .. self:Replace(str:sub(e + 1), this, that) + end +end + +_G.Utilities = Utilities +_ENV.Utilities = Utilities +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +local assert = assert +---@type Utilities +local U = assert(Utilities) +local type = assert(type) +local pairs = assert(pairs) +local lower = assert(string.lower) +local upper = assert(string.upper) +local sub = assert(string.sub) +local pack = assert(table.pack) +local unpack = assert(table.unpack) +local insert = assert(table.insert) +local rawset = assert(rawset) +local rawget = assert(rawget) +local setmetatable = assert(setmetatable) + +--- FiveM globals +local CreateThread = assert(Citizen.CreateThread) + +--- Create a new menu item +---@param info table Menu information +---@return Item New item +function CreateMenuItem(info) + info = U:Ensure(info, {}) + + local item = { + ---@type Menu|nil + __menu = U:Ensure(info.__Menu or info.__menu, { __class = 'Menu', __type = 'Menu' }, true) or nil, + ---@type string + __event = U:Ensure(info.PrimaryEvent or info.primaryEvent, 'unknown'), + ---@type string + UUID = U:UUID(), + ---@type string + Icon = U:Ensure(info.Icon or info.icon, 'none'), + ---@type string + Label = U:Ensure(info.Label or info.label, ''), + ---@type string + Description = U:Ensure(info.Description or info.description, ''), + ---@type any + Value = info.Value or info.value, + ---@type table[] + Values = {}, + ---@type number + Min = U:Ensure(info.Min or info.min, 0), + ---@type number + Max = U:Ensure(info.Max or info.max, 0), + ---@type boolean + Disabled = U:Ensure(info.Disabled or info.disabled, false), + ---@type table + Events = U:Ensure(info.Events or info.events, {}), + ---@type boolean + SaveOnUpdate = U:Ensure(info.SaveOnUpdate or info.saveOnUpdate, false), + ---@param t Item + ---@param event string Name of Event + Trigger = function(t, event, ...) + event = lower(U:Ensure(event, 'unknown')) + + if (event == 'unknown') then return end + if (U:StartsWith(event, 'on')) then + event = 'On' .. sub(event, 3):gsub('^%l', upper) + else + event = 'On' .. event:gsub('^%l', upper) + end + + if (not U:Any(event, (t.Events or {}), 'key')) then + return + end + + local args = pack(...) + + for _, v in pairs(t.Events[event]) do + CreateThread(function() + v(t, unpack(args)) + end) + end + end, + ---@param t Item + ---@param event string Name of event + ---@param func function|Menu Function or Menu to trigger + On = function(t, event, func) + event = lower(U:Ensure(event, 'unknown')) + + if (event == 'unknown') then return end + if (U:StartsWith(event, 'on')) then + event = 'On' .. sub(event, 3):gsub('^%l', upper) + else + event = 'On' .. event:gsub('^%l', upper) + end + + if (not U:Any(event, (t.Events or {}), 'key')) then + return + end + + local _type = U:Typeof(func) + + if (_type == 'Menu') then + local menu_t = { + __class = 'function', + __type = 'function', + func = function(t) MenuV:OpenMenu(t.uuid) end, + uuid = func.UUID or func.uuid or U:UUID() + } + local menu_mt = { __index = menu_t, __call = function(t) t:func() end } + local menu_item = setmetatable(menu_t, menu_mt) + + insert(t.Events[event], menu_item) + + return + end + + func = U:Ensure(func, function() end) + + insert(t.Events[event], func) + end, + ---@param t Item + ---@param k string + ---@param v string + Validate = U:Ensure(info.Validate or info.validate, function(t, k, v) + return true + end), + ---@param t Item + ---@param k string + ---@param v string + Parser = U:Ensure(info.Parser or info.parser, function(t, k, v) + return v + end), + ---@param t Item + ---@param k string + ---@param v string + NewIndex = U:Ensure(info.NewIndex or info.newIndex, function(t, k, v) + end), + ---@param t Item + ---@return any + GetValue = function(t) + local itemType = U:Ensure(t.__type, 'unknown') + + if (itemType == 'button' or itemType == 'menu' or itemType == 'unknown') then + return t.Value + end + + if (itemType == 'checkbox' or itemType == 'confirm') then + return U:Ensure(t.Value, false) + end + + if (itemType == 'slider') then + for _, item in pairs(t.Values) do + if (item.Value == t.Value) then + return item.Value + end + end + + return nil + end + + if (itemType == 'range') then + local rawValue = U:Ensure(t.Value, 0) + + if (t.Min > rawValue) then + return t.Min + end + + if (t.Max < rawValue) then + return t.Max + end + + return rawValue + end + end, + ---@return Menu|nil + GetParentMenu = function(t) + return t.__menu or nil + end + } + + item.Events.OnEnter = {} + item.Events.OnLeave = {} + item.Events.OnUpdate = {} + item.Events.OnDestroy = {} + + local mt = { + __index = function(t, k) + return rawget(t.data, k) + end, + __tostring = function(t) + return t.UUID + end, + __call = function(t, ...) + if (t.Trigger ~= nil and type(t.Trigger) == 'function') then + t:Trigger(t.__event, ...) + end + end, + __newindex = function(t, k, v) + local key = U:Ensure(k, 'unknown') + local oldValue = rawget(t.data, k) + local checkInput = t.Validate ~= nil and type(t.Validate) == 'function' + local inputParser = t.Parser ~= nil and type(t.Parser) == 'function' + local updateIndexTrigger = t.NewIndex ~= nil and type(t.NewIndex) == 'function' + + if (checkInput) then + local result = t:Validate(key, v) + result = U:Ensure(result, true) + + if (not result) then + return + end + end + + if (inputParser) then + local parsedValue = t:Parser(key, v) + + v = parsedValue or v + end + + rawset(t.data, k, v) + + if (updateIndexTrigger) then + t:NewIndex(key, v) + end + + if (t.__menu ~= nil and U:Typeof(t.__menu) == 'Menu' and t.__menu.Trigger ~= nil and U:Typeof( t.__menu.Trigger) == 'function') then + t.__menu:Trigger('update', 'UpdateItem', t) + end + + if (key == 'Value' and t.Trigger ~= nil and type(t.Trigger) == 'function') then + t:Trigger('update', key, v, oldValue) + end + end, + __metatable = 'MenuV' + } + + ---@class Item + ---@filed private __event string Name of primary event + ---@field public UUID string UUID of Item + ---@field public Icon string Icon/Emoji for Item + ---@field public Label string Label of Item + ---@field public Description string Description of Item + ---@field public Value any Value of Item + ---@field public Values table[] List of values + ---@field public Min number Min range value + ---@field public Max number Max range value + ---@field public Disabled boolean Disabled state of Item + ---@field public SaveOnUpdate boolean Save on `update` + ---@field private Events table List of registered `on` events + ---@field public Trigger fun(t: Item, event: string) + ---@field public On fun(t: Item, event: string, func: function|Menu) + ---@field public Validate fun(t: Item, k: string, v:any) + ---@field public NewIndex fun(t: Item, k: string, v: any) + ---@field public Parser fun(t: Item, k: string, v: any) + ---@field public GetValue fun(t: Item):any + ---@field public GetParentMenu func(t: Item):Menu|nil + local i = setmetatable({ data = item, __class = 'Item', __type = U:Ensure(info.Type or info.type, 'unknown') }, mt) + + for k, v in pairs(info or {}) do + local key = U:Ensure(k, 'unknown') + + if (key == 'unknown') then return end + + i:On(key, v) + end + + return i +end + +_ENV.CreateMenuItem = CreateMenuItem +_G.CreateMenuItem = CreateMenuItem +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +local assert = assert +---@type Utilities +local U = assert(Utilities) +local type = assert(type) +local next = assert(next) +local pairs = assert(pairs) +local ipairs = assert(ipairs) +local lower = assert(string.lower) +local upper = assert(string.upper) +local sub = assert(string.sub) +local insert = assert(table.insert) +local remove = assert(table.remove) +local pack = assert(table.pack) +local unpack = assert(table.unpack) +local encode = assert(json.encode) +local rawset = assert(rawset) +local rawget = assert(rawget) +local setmetatable = assert(setmetatable) + +--- FiveM globals +local GET_CURRENT_RESOURCE_NAME = assert(GetCurrentResourceName) +local GET_INVOKING_RESOURCE = assert(GetInvokingResource) +local HAS_STREAMED_TEXTURE_DICT_LOADED = assert(HasStreamedTextureDictLoaded) +local REQUEST_STREAMED_TEXTURE_DICT = assert(RequestStreamedTextureDict) + +--- MenuV local variable +local current_resource = GET_CURRENT_RESOURCE_NAME() + +--- Returns default empty table for items +---@returns items +function CreateEmptyItemsTable(data) + data = U:Ensure(data, {}) + data.ToTable = function(t) + local tempTable = {} + local index = 0 + + for _, option in pairs(t) do + index = index + 1 + + tempTable[index] = { + index = index, + type = option.__type, + uuid = U:Ensure(option.UUID, 'unknown'), + icon = U:Ensure(option.Icon, 'none'), + label = U:Ensure(option.Label, 'Unknown'), + description = U:Ensure(option.Description, ''), + value = 'none', + values = {}, + min = U:Ensure(option.Min, 0), + max = U:Ensure(option.Max, 0), + disabled = U:Ensure(option.Disabled, false) + } + + if (option.__type == 'button' or option.__type == 'menu') then + tempTable[index].value = 'none' + elseif (option.__type == 'checkbox' or option.__type == 'confirm') then + tempTable[index].value = U:Ensure(option.Value, false) + elseif (option.__type == 'range') then + tempTable[index].value = U:Ensure(option.Value, 0) + + if (tempTable[index].value <= tempTable[index].min) then + tempTable[index].value = tempTable[index].min + elseif (tempTable[index].value >= tempTable[index].max) then + tempTable[index].value = tempTable[index].max + end + elseif (option.__type == 'slider') then + tempTable[index].value = 0 + end + + local _values = U:Ensure(option.Values, {}) + local vIndex = 0 + + for valueIndex, value in pairs(_values) do + vIndex = vIndex + 1 + + tempTable[index].values[vIndex] = { + label = U:Ensure(value.Label, 'Option'), + description = U:Ensure(value.Description, ''), + value = vIndex + } + + if (option.__type == 'slider') then + if (U:Ensure(option.Value, 0) == valueIndex) then + tempTable[index].value = (valueIndex - 1) + end + end + end + end + + return tempTable + end + data.ItemToTable = function(t, i) + local tempTable = {} + local index = 0 + local uuid = U:Typeof(i) == 'Item' and i.UUID or U:Ensure(i, '00000000-0000-0000-0000-000000000000') + + for _, option in pairs(t) do + index = index + 1 + + if (option.UUID == uuid) then + tempTable = { + index = index, + type = option.__type, + uuid = U:Ensure(option.UUID, 'unknown'), + icon = U:Ensure(option.Icon, 'none'), + label = U:Ensure(option.Label, 'Unknown'), + description = U:Ensure(option.Description, ''), + value = 'none', + values = {}, + min = U:Ensure(option.Min, 0), + max = U:Ensure(option.Max, 0), + disabled = U:Ensure(option.Disabled, false) + } + + if (option.__type == 'button' or option.__type == 'menu') then + tempTable.value = 'none' + elseif (option.__type == 'checkbox' or option.__type == 'confirm') then + tempTable.value = U:Ensure(option.Value, false) + elseif (option.__type == 'range') then + tempTable.value = U:Ensure(option.Value, 0) + + if (tempTable.value <= tempTable.min) then + tempTable.value = tempTable.min + elseif (tempTable.value >= tempTable.max) then + tempTable.value = tempTable.max + end + elseif (option.__type == 'slider') then + tempTable.value = 0 + end + + local _values = U:Ensure(option.Values, {}) + local vIndex = 0 + + for valueIndex, value in pairs(_values) do + vIndex = vIndex + 1 + + tempTable.values[vIndex] = { + label = U:Ensure(value.Label, 'Option'), + description = U:Ensure(value.Description, ''), + value = vIndex + } + + if (option.__type == 'slider') then + if (U:Ensure(option.Value, 0) == valueIndex) then + tempTable.value = (valueIndex - 1) + end + end + end + + return tempTable + end + end + + return tempTable + end + data.AddItem = function(t, item) + if (U:Typeof(item) == 'Item') then + local newIndex = #(U:Ensure(rawget(t, 'data'), {})) + 1 + + rawset(t.data, newIndex, item) + + if (t.Trigger ~= nil and type(t.Trigger) == 'function') then + t:Trigger('update', 'AddItem', item) + end + end + + return U:Ensure(rawget(t, 'data'), {}) + end + + local item_pairs = function(t, k) + local _k, _v = next((rawget(t, 'data') or {}), k) + + if (_v ~= nil and type(_v) ~= 'table') then + return item_pairs(t, _k) + end + + return _k, _v + end + + local item_ipairs = function(t, k) + local _k, _v = next((rawget(t, 'data') or {}), k) + + if (_v ~= nil and (type(_v) ~= 'table' or type(_k) ~= 'number')) then + return item_ipairs(t, _k) + end + + return _k, _v + end + + _G.item_pairs = item_pairs + _ENV.item_pairs = item_pairs + + ---@class items + return setmetatable({ data = data, Trigger = nil }, { + __index = function(t, k) + return rawget(t.data, k) + end, + __newindex = function(t, k, v) + local oldValue = rawget(t.data, k) + + rawset(t.data, k, v) + + if (t.Trigger ~= nil and type(t.Trigger) == 'function') then + if (oldValue == nil) then + t:Trigger('update', 'AddItem', v) + elseif (oldValue ~= nil and v == nil) then + t:Trigger('update', 'RemoveItem', oldValue) + elseif (oldValue ~= v) then + t:Trigger('update', 'UpdateItem', v, oldValue) + end + end + end, + __call = function(t, func) + rawset(t, 'Trigger', U:Ensure(func, function() end)) + end, + __pairs = function(t) + local k = nil + + return function() + local v + + k, v = item_pairs(t, k) + + return k, v + end, t, nil + end, + __ipairs = function(t) + local k = nil + + return function() + local v + + k, v = item_ipairs(t, k) + + return k, v + end, t, 0 + end, + __len = function(t) + local items = U:Ensure(rawget(t, 'data'), {}) + local itemCount = 0 + + for _, v in pairs(items) do + if (U:Typeof(v) == 'Item') then + itemCount = itemCount + 1 + end + end + + return itemCount + end + }) +end + +--- Load a texture dictionary if not already loaded +---@param textureDictionary string Name of texture dictionary +local function LoadTextureDictionary(textureDictionary) + textureDictionary = U:Ensure(textureDictionary, 'menuv') + + if (HAS_STREAMED_TEXTURE_DICT_LOADED(textureDictionary)) then return end + + REQUEST_STREAMED_TEXTURE_DICT(textureDictionary, true) +end + +--- Create a new menu item +---@param info table Menu information +---@return Menu New item +function CreateMenu(info) + info = U:Ensure(info, {}) + + local namespace = U:Ensure(info.Namespace or info.namespace, 'unknown') + local namespace_available = MenuV:IsNamespaceAvailable(namespace) + + if (not namespace_available) then + error(("[MenuV] Namespace '%s' is already taken, make sure it is unique."):format(namespace)) + end + + local theme = lower(U:Ensure(info.Theme or info.theme, 'default')) + + if (theme ~= 'default' and theme ~= 'native') then + theme = 'default' + end + + if (theme == 'native') then + info.R, info.G, info.B = 255, 255, 255 + info.r, info.g, info.b = 255, 255, 255 + end + + local item = { + ---@type string + Namespace = namespace, + ---@type boolean + IsOpen = false, + ---@type string + UUID = U:UUID(), + ---@type string + Title = not (info.Title or info.title) and '‏‏‎ ‎' or U:Ensure(info.Title or info.title, 'MenuV'), + ---@type string + Subtitle = U:Ensure(info.Subtitle or info.subtitle, ''), + ---@type string | "'topleft'" | "'topcenter'" | "'topright'" | "'centerleft'" | "'center'" | "'centerright'" | "'bottomleft'" | "'bottomcenter'" | "'bottomright'" + Position = U:Ensure(info.Position or info.position, 'topleft'), + ---@type table + Color = { + R = U:Ensure(info.R or info.r, 0), + G = U:Ensure(info.G or info.g, 0), + B = U:Ensure(info.B or info.b, 255) + }, + ---@type string | "'size-100'" | "'size-110'" | "'size-125'" | "'size-150'" | "'size-175'" | "'size-200'" + Size = U:Ensure(info.Size or info.size, 'size-110'), + ---@type string + Dictionary = U:Ensure(info.Dictionary or info.dictionary, 'menuv'), + ---@type string + Texture = U:Ensure(info.Texture or info.texture, 'default'), + ---@type table + Events = U:Ensure(info.Events or info.events, {}), + ---@type string + Theme = theme, + ---@type Item[] + Items = CreateEmptyItemsTable({}), + ---@param t Menu + ---@param event string Name of Event + Trigger = function(t, event, ...) + event = lower(U:Ensure(event, 'unknown')) + + if (event == 'unknown') then return end + if (U:StartsWith(event, 'on')) then + event = 'On' .. sub(event, 3):gsub('^%l', upper) + else + event = 'On' .. event:gsub('^%l', upper) + end + + if (not U:Any(event, (t.Events or {}), 'key')) then + return + end + + if (event == 'OnOpen') then rawset(t, 'IsOpen', true) + elseif (event == 'OnClose') then rawset(t, 'IsOpen', false) end + + local args = pack(...) + + for _, v in pairs(t.Events[event]) do + if (type(v) == 'table' and U:Typeof(v.func) == 'function') then + CreateThread(function() + if (event == 'OnClose') then + v.func(t, unpack(args)) + else + local threadId = coroutine.running() + + if (threadId ~= nil) then + insert(t.data.Threads, threadId) + + v.func(t, unpack(args)) + + for i = 0, #(t.data.Threads or {}), 1 do + if (t.data.Threads[i] == threadId) then + remove(t.data.Threads, i) + return + end + end + end + end + end) + end + end + end, + ---@type thread[] + Threads = {}, + ---@param t Menu + DestroyThreads = function(t) + for _, threadId in pairs(t.data.Threads or {}) do + local threadStatus = coroutine.status(threadId) + + if (threadStatus ~= nil and threadStatus ~= 'dead') then + coroutine.close(threadId) + end + end + + t.data.Threads = {} + end, + ---@param t Menu + ---@param event string Name of event + ---@param func function|Menu Function or Menu to trigger + ---@return string UUID of event + On = function(t, event, func) + local ir = GET_INVOKING_RESOURCE() + local resource = U:Ensure(ir, current_resource) + + event = lower(U:Ensure(event, 'unknown')) + + if (event == 'unknown') then return end + if (U:StartsWith(event, 'on')) then + event = 'On' .. sub(event, 3):gsub('^%l', upper) + else + event = 'On' .. event:gsub('^%l', upper) + end + + if (not U:Any(event, (t.Events or {}), 'key')) then + return + end + + func = U:Ensure(func, function() end) + + local uuid = U:UUID() + + insert(t.Events[event], { + __uuid = uuid, + __resource = resource, + func = func + }) + + return uuid + end, + ---@param t Menu + ---@param event string Name of event + ---@param uuid string UUID of event + RemoveOnEvent = function(t, event, uuid) + local ir = GET_INVOKING_RESOURCE() + local resource = U:Ensure(ir, current_resource) + + event = lower(U:Ensure(event, 'unknown')) + + if (event == 'unknown') then return end + if (U:StartsWith(event, 'on')) then + event = 'On' .. sub(event, 3):gsub('^%l', upper) + else + event = 'On' .. event:gsub('^%l', upper) + end + + if (not U:Any(event, (t.Events or {}), 'key')) then + return + end + + uuid = U:Ensure(uuid, '00000000-0000-0000-0000-000000000000') + + for i = 1, #t.Events[event], 1 do + if (t.Events[event][i] ~= nil and + t.Events[event][i].__uuid == uuid and + t.Events[event][i].__resource == resource) then + remove(t.Events[event], i) + end + end + end, + ---@param t Item + ---@param k string + ---@param v string + Validate = U:Ensure(info.Validate or info.validate, function(t, k, v) + return true + end), + ---@param t Item + ---@param k string + ---@param v string + Parser = function(t, k, v) + if (k == 'Position' or k == 'position') then + local position = lower(U:Ensure(v, 'topleft')) + + if (U:Any(position, {'topleft', 'topcenter', 'topright', 'centerleft', 'center', 'centerright', 'bottomleft', 'bottomcenter', 'bottomright'}, 'value')) then + return position + else + return 'topleft' + end + end + + return v + end, + ---@param t Item + ---@param k string + ---@param v string + NewIndex = U:Ensure(info.NewIndex or info.newIndex, function(t, k, v) + end), + ---@type function + ---@param t Menu MenuV menu + ---@param info table Information about button + ---@return Item New item + AddButton = function(t, info) + info = U:Ensure(info, {}) + + info.Type = 'button' + info.Events = { OnSelect = {} } + info.PrimaryEvent = 'OnSelect' + info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false) + info.__menu = t + + if (U:Typeof(info.Value or info.value) == 'Menu') then + info.Type = 'menu' + end + + local item = CreateMenuItem(info) + + if (info.Type == 'menu') then + item:On('select', function() item.Value() end) + end + + if (info.TriggerUpdate) then + t.Items:AddItem(item) + else + local items = rawget(t.data, 'Items') + + if (items) then + local newIndex = #items + 1 + + rawset(items.data, newIndex, item) + + return items.data[newIndex] or item + end + end + + return t.Items[#t.Items] or item + end, + ---@type function + ---@param t Menu MenuV menu + ---@param info table Information about checkbox + ---@return Item New item + AddCheckbox = function(t, info) + info = U:Ensure(info, {}) + + info.Type = 'checkbox' + info.Value = U:Ensure(info.Value or info.value, false) + info.Events = { OnChange = {}, OnCheck = {}, OnUncheck = {} } + info.PrimaryEvent = 'OnCheck' + info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false) + info.__menu = t + info.NewIndex = function(t, k, v) + if (k == 'Value') then + local value = U:Ensure(v, false) + + if (value) then + t:Trigger('check', t) + else + t:Trigger('uncheck', t) + end + end + end + + local item = CreateMenuItem(info) + + if (info.TriggerUpdate) then + t.Items:AddItem(item) + else + local items = rawget(t.data, 'Items') + + if (items) then + local newIndex = #items + 1 + + rawset(items.data, newIndex, item) + + return items.data[newIndex] or item + end + end + + return t.Items[#t.Items] or item + end, + ---@type function + ---@param t Menu MenuV menu + ---@param info table Information about slider + ---@return SliderItem New slider item + AddSlider = function(t, info) + info = U:Ensure(info, {}) + + info.Type = 'slider' + info.Events = { OnChange = {}, OnSelect = {} } + info.PrimaryEvent = 'OnSelect' + info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false) + info.__menu = t + + ---@class SliderItem : Item + ---@filed private __event string Name of primary event + ---@field public UUID string UUID of Item + ---@field public Icon string Icon/Emoji for Item + ---@field public Label string Label of Item + ---@field public Description string Description of Item + ---@field public Value any Value of Item + ---@field public Values table[] List of values + ---@field public Min number Min range value + ---@field public Max number Max range value + ---@field public Disabled boolean Disabled state of Item + ---@field private Events table List of registered `on` events + ---@field public Trigger fun(t: Item, event: string) + ---@field public On fun(t: Item, event: string, func: function) + ---@field public Validate fun(t: Item, k: string, v:any) + ---@field public NewIndex fun(t: Item, k: string, v: any) + ---@field public GetValue fun(t: Item):any + ---@field public AddValue fun(t: Item, info: table) + ---@field public AddValues fun(t: Item) + local item = CreateMenuItem(info) + + --- Add a value to slider + ---@param info table Information about slider + function item:AddValue(info) + info = U:Ensure(info, {}) + + local value = { + Label = U:Ensure(info.Label or info.label, 'Value'), + Description = U:Ensure(info.Description or info.description, ''), + Value = info.Value or info.value + } + + insert(self.Values, value) + end + + --- Add values to slider + ---@vararg table[] List of values + function item:AddValues(...) + local arguments = pack(...) + + for _, argument in pairs(arguments) do + if (U:Typeof(argument) == 'table') then + local hasIndex = argument[1] or nil + + if (hasIndex and U:Typeof(hasIndex) == 'table') then + self:AddValues(unpack(argument)) + else + self:AddValue(argument) + end + end + end + end + + local values = U:Ensure(info.Values or info.values, {}) + + if (#values > 0) then + item:AddValues(values) + end + + if (info.TriggerUpdate) then + t.Items:AddItem(item) + else + local items = rawget(t.data, 'Items') + + if (items) then + local newIndex = #items + 1 + + rawset(items.data, newIndex, item) + + return items.data[newIndex] or item + end + end + + return t.Items[#t.Items] or item + end, + ---@type function + ---@param t Menu MenuV menu + ---@param info table Information about range + ---@return RangeItem New Range item + AddRange = function(t, info) + info = U:Ensure(info, {}) + + info.Type = 'range' + info.Events = { OnChange = {}, OnSelect = {}, OnMin = {}, OnMax = {} } + info.PrimaryEvent = 'OnSelect' + info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false) + info.__menu = t + info.Value = U:Ensure(info.Value or info.value, 0) + info.Min = U:Ensure(info.Min or info.min, 0) + info.Max = U:Ensure(info.Max or info.max, 0) + info.Validate = function(t, k, v) + if (k == 'Value' or k == 'value') then + v = U:Ensure(v, 0) + + if (t.Min > v) then return false end + if (t.Max < v) then return false end + end + + return true + end + + if (info.Min > info.Max) then + local min = info.Min + local max = info.Max + + info.Min = min + info.Max = max + end + + if (info.Value < info.Min) then info.Value = info.Min end + if (info.Value > info.Max) then info.Value = info.Max end + + ---@class RangeItem : Item + ---@filed private __event string Name of primary event + ---@field public UUID string UUID of Item + ---@field public Icon string Icon/Emoji for Item + ---@field public Label string Label of Item + ---@field public Description string Description of Item + ---@field public Value any Value of Item + ---@field public Values table[] List of values + ---@field public Min number Min range value + ---@field public Max number Max range value + ---@field public Disabled boolean Disabled state of Item + ---@field private Events table List of registered `on` events + ---@field public Trigger fun(t: Item, event: string) + ---@field public On fun(t: Item, event: string, func: function) + ---@field public Validate fun(t: Item, k: string, v:any) + ---@field public NewIndex fun(t: Item, k: string, v: any) + ---@field public GetValue fun(t: Item):any + ---@field public SetMinValue fun(t: any) + ---@field public SetMaxValue fun(t: any) + local item = CreateMenuItem(info) + + --- Update min value of range + ---@param input number Minimum value of Range + function item:SetMinValue(input) + input = U:Ensure(input, 0) + + self.Min = input + + if (self.Value < self.Min) then + self.Value = self.Min + end + + if (self.Min > self.Max) then + self.Max = self.Min + end + end + + --- Update max value of range + ---@param input number Minimum value of Range + function item:SetMaxValue(input) + input = U:Ensure(input, 0) + + self.Min = input + + if (self.Value > self.Max) then + self.Value = self.Max + end + + if (self.Min < self.Max) then + self.Min = self.Max + end + end + + if (info.TriggerUpdate) then + t.Items:AddItem(item) + else + local items = rawget(t.data, 'Items') + + if (items) then + local newIndex = #items + 1 + + rawset(items.data, newIndex, item) + + return items.data[newIndex] or item + end + end + + return t.Items[#t.Items] or item + end, + ---@type function + ---@param t Menu MenuV menu + ---@param info table Information about confirm + ---@return ConfirmItem New Confirm item + AddConfirm = function(t, info) + info = U:Ensure(info, {}) + + info.Type = 'confirm' + info.Value = U:Ensure(info.Value or info.value, false) + info.Events = { OnConfirm = {}, OnDeny = {}, OnChange = {} } + info.PrimaryEvent = 'OnConfirm' + info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false) + info.__menu = t + info.NewIndex = function(t, k, v) + if (k == 'Value') then + local value = U:Ensure(v, false) + + if (value) then + t:Trigger('confirm', t) + else + t:Trigger('deny', t) + end + end + end + + ---@class ConfirmItem : Item + ---@filed private __event string Name of primary event + ---@field public UUID string UUID of Item + ---@field public Icon string Icon/Emoji for Item + ---@field public Label string Label of Item + ---@field public Description string Description of Item + ---@field public Value any Value of Item + ---@field public Values table[] List of values + ---@field public Min number Min range value + ---@field public Max number Max range value + ---@field public Disabled boolean Disabled state of Item + ---@field private Events table List of registered `on` events + ---@field public Trigger fun(t: Item, event: string) + ---@field public On fun(t: Item, event: string, func: function) + ---@field public Validate fun(t: Item, k: string, v:any) + ---@field public NewIndex fun(t: Item, k: string, v: any) + ---@field public GetValue fun(t: Item):any + ---@field public Confirm fun(t: Item) + ---@field public Deny fun(t: Item) + local item = CreateMenuItem(info) + + --- Confirm this item + function item:Confirm() item.Value = true end + --- Deny this item + function item:Deny() item.Value = false end + + if (info.TriggerUpdate) then + t.Items:AddItem(item) + else + local items = rawget(t.data, 'Items') + + if (items) then + local newIndex = #items + 1 + + rawset(items.data, newIndex, item) + + return items.data[newIndex] or item + end + end + + return t.Items[#t.Items] or item + end, + --- Create child menu from properties of this object + ---@param t Menu|string MenuV menu + ---@param overrides table Properties to override in menu object (ignore parent) + ---@param namespace string Namespace of menu + InheritMenu = function(t, overrides, namespace) + return MenuV:InheritMenu(t, overrides, namespace) + end, + --- Add control key for specific menu + ---@param t Menu|string MenuV menu + ---@param action string Name of action + ---@param func function This will be executed + ---@param description string Key description + ---@param defaultType string Default key type + ---@param defaultKey string Default key + AddControlKey = function(t, action, func, description, defaultType, defaultKey) + if (U:Typeof(t.Namespace) ~= 'string' or t.Namespace == 'unknown') then + error('[MenuV] Namespace is required for assigning keys.') + return + end + + MenuV:AddControlKey(t, action, func, description, defaultType, defaultKey) + end, + --- Assign key for opening this menu + ---@param t Menu|string MenuV menu + ---@param defaultType string Default key type + ---@param defaultKey string Default key + OpenWith = function(t, defaultType, defaultKey) + t:AddControlKey('open', function(m) + MenuV:CloseAll(function() + MenuV:OpenMenu(m) + end) + end, MenuV:T('open_menu'):format(t.Namespace), defaultType, defaultKey) + end, + --- Change title of menu + ---@param t Menu + ---@param title string Title of menu + SetTitle = function(t, title) + t.Title = U:Ensure(title, 'MenuV') + end, + --- Change subtitle of menu + ---@param t Menu + ---@param subtitle string Subtitle of menu + SetSubtitle = function(t, subtitle) + t.Subtitle = U:Ensure(subtitle, '') + end, + --- Change subtitle of menu + ---@param t Menu + ---@param position string | "'topleft'" | "'topcenter'" | "'topright'" | "'centerleft'" | "'center'" | "'centerright'" | "'bottomleft'" | "'bottomcenter'" | "'bottomright'" + SetPosition = function(t, position) + t.Position = U:Ensure(position, 'topleft') + end, + --- Clear all Menu items + ---@param t Menu + ClearItems = function(t, update) + update = U:Ensure(update, true) + + local items = CreateEmptyItemsTable({}) + + items(function(_, trigger, key, index, value, oldValue) + t:Trigger(trigger, key, index, value, oldValue) + end) + + rawset(t.data, 'Items', items) + + if (update and t.Trigger ~= nil and type(t.Trigger) == 'function') then + t:Trigger('update', 'Items', items) + end + end, + Open = function(t) + MenuV:OpenMenu(t) + end, + Close = function(t) + MenuV:CloseMenu(t) + end, + --- @see Menu to @see table + ---@param t Menu + ---@return table + ToTable = function(t) + local tempTable = { + theme = U:Ensure(t.Theme, 'default'), + uuid = U:Ensure(t.UUID, '00000000-0000-0000-0000-000000000000'), + title = U:Ensure(t.Title, 'MenuV'), + subtitle = U:Ensure(t.Subtitle, ''), + position = U:Ensure(t.Position, 'topleft'), + size = U:Ensure(t.Size, 'size-110'), + dictionary = U:Ensure(t.Dictionary, 'menuv'), + texture = U:Ensure(t.Texture, 'default'), + color = { + r = U:Ensure(t.Color.R, 0), + g = U:Ensure(t.Color.G, 0), + b = U:Ensure(t.Color.B, 255) + }, + items = {} + } + + local items = rawget(t.data, 'Items') + + if (items ~= nil and items.ToTable ~= nil) then + tempTable.items = items:ToTable() + end + + if (tempTable.color.r <= 0) then tempTable.color.r = 0 end + if (tempTable.color.r >= 255) then tempTable.color.r = 255 end + if (tempTable.color.g <= 0) then tempTable.color.g = 0 end + if (tempTable.color.g >= 255) then tempTable.color.g = 255 end + if (tempTable.color.b <= 0) then tempTable.color.b = 0 end + if (tempTable.color.b >= 255) then tempTable.color.b = 255 end + + return tempTable + end + } + + if (lower(item.Texture) == 'default' and lower(item.Dictionary) == 'menuv' and theme == 'native') then + item.Texture = 'default_native' + end + + item.Events.OnOpen = {} + item.Events.OnClose = {} + item.Events.OnSelect = {} + item.Events.OnUpdate = {} + item.Events.OnSwitch = {} + item.Events.OnChange = {} + item.Events.OnIChange = {} + + if (not U:Any(item.Size, { 'size-100', 'size-110', 'size-125', 'size-150', 'size-175', 'size-200' }, 'value')) then + item.Size = 'size-110' + end + + local mt = { + __index = function(t, k) + return rawget(t.data, k) + end, + ---@param t Menu + __tostring = function(t) + return encode(t:ToTable()) + end, + __call = function(t) + MenuV:OpenMenu(t) + end, + __newindex = function(t, k, v) + local whitelisted = { 'Title', 'Subtitle', 'Position', 'Color', 'R', 'G', 'B', 'Size', 'Dictionary', 'Texture', 'Theme' } + local key = U:Ensure(k, 'unknown') + local oldValue = rawget(t.data, k) + + if (not U:Any(key, whitelisted, 'value') and oldValue ~= nil) then + return + end + + local checkInput = t.Validate ~= nil and type(t.Validate) == 'function' + local inputParser = t.Parser ~= nil and type(t.Parser) == 'function' + local updateIndexTrigger = t.NewIndex ~= nil and type(t.NewIndex) == 'function' + + if (checkInput) then + local result = t:Validate(key, v) + result = U:Ensure(result, true) + + if (not result) then + return + end + end + + if (inputParser) then + local parsedValue = t:Parser(key, v) + + v = parsedValue or v + end + + rawset(t.data, k, v) + + if (key == 'Theme' or key == 'theme') then + local theme_value = string.lower(U:Ensure(v, 'default')) + + if (theme_value == 'native') then + rawset(t.data, 'color', { R = 255, G = 255, B = 255 }) + + local texture = U:Ensure(rawget(t.data, 'Texture'), 'default') + + if (texture == 'default') then + rawset(t.data, 'Texture', 'default_native') + end + elseif (theme_value == 'default') then + local texture = U:Ensure(rawget(t.data, 'Texture'), 'default') + + if (texture == 'default_native') then + rawset(t.data, 'Texture', 'default') + end + end + end + + if (updateIndexTrigger) then + t:NewIndex(key, v) + end + + if (t.Trigger ~= nil and type(t.Trigger) == 'function') then + t:Trigger('update', key, v, oldValue) + end + end, + __len = function(t) + return #t.Items + end, + __pairs = function(t) + return pairs(rawget(t.data, 'Items') or {}) + end, + __ipairs = function(t) + return ipairs(rawget(t.data, 'Items') or {}) + end, + __metatable = 'MenuV', + } + + ---@class Menu + ---@field public IsOpen boolean `true` if menu is open, otherwise `false` + ---@field public UUID string UUID of Menu + ---@field public Title string Title of Menu + ---@field public Subtitle string Subtitle of Menu + ---@field public Position string | "'topleft'" | "'topcenter'" | "'topright'" | "'centerleft'" | "'center'" | "'centerright'" | "'bottomleft'" | "'bottomcenter'" | "'bottomright'" + ---@field public Texture string Name of texture example: "default" + ---@field public Dictionary string Name of dictionary example: "menuv" + ---@field public Color table Color of Menu + ---@field private Events table List of registered `on` events + ---@field public Items Item[] List of items + ---@field public Trigger fun(t: Item, event: string) + ---@field public On fun(t: Menu, event: string, func: function|Menu): string + ---@field public RemoveOnEvent fun(t: Menu, event: string, uuid: string) + ---@field public Validate fun(t: Menu, k: string, v:any) + ---@field public NewIndex fun(t: Menu, k: string, v: any) + ---@field public Parser fun(t: Menu, k: string, v: any) + ---@field public AddButton fun(t: Menu, info: table):Item + ---@field public AddCheckbox fun(t: Menu, info: table):Item + ---@field public AddSlider fun(t: Menu, info: table):SliderItem + ---@field public AddRange fun(t: Menu, info: table):RangeItem + ---@field public AddConfirm fun(t: Menu, info: table):ConfirmItem + ---@field public AddControlKey fun(t: Menu, action: string, func: function, description: string, defaultType: string, defaultKey: string) + ---@field public OpenWith fun(t: Menu, defaultType: string, defaultKey: string) + ---@field public SetTitle fun(t: Menu, title: string) + ---@field public SetSubtitle fun(t: Menu, subtitle: string) + ---@field public SetPosition fun(t: Menu, position: string) + ---@field public ClearItems fun(t: Menu) + ---@field public Open fun(t: Menu) + ---@field public Close fun(t: Menu) + ---@field public ToTable fun(t: Menu):table + local menu = setmetatable({ data = item, __class = 'Menu', __type = 'Menu' }, mt) + + menu.Items(function(items, trigger, key, index, value, oldValue) + menu:Trigger(trigger, key, index, value, oldValue) + end) + + for k, v in pairs(info or {}) do + local key = U:Ensure(k, 'unknown') + + if (key == 'unknown') then return end + + menu:On(key, v) + end + + LoadTextureDictionary(menu.Dictionary) + + return menu +end + +_ENV.CreateMenu = CreateMenu +_G.CreateMenu = CreateMenu + +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +local assert = assert +local decode = assert(json.decode) + +--- FiveM globals +local LoadResourceFile = assert(LoadResourceFile) + +--- MenuV globals +---@type Utilities +local Utilities = assert(Utilities) + +--- Empty translations table +local translations = {} + +--- Load all translations +local lang = Utilities:Ensure((Config or {}).Language, 'en') +local translations_path = ('languages/%s.json'):format(lang) +local translations_raw = LoadResourceFile('menuv', translations_path) + +if (translations_raw) then + local transFile = decode(translations_raw) + + if (transFile) then translations = Utilities:Ensure(transFile.translations, {}) end +end + +_ENV.translations = translations +_G.translations = translations + +--- MenuV table +local menuv_table = { + ---@type string + __class = 'MenuV', + ---@type string + __type = 'MenuV', + ---@type Menu|nil + CurrentMenu = nil, + ---@type string|nil + CurrentUpdateUUID = nil, + ---@type string + CurrentResourceName = GET_CURRENT_RESOURCE_NAME(), + ---@type boolean + Loaded = false, + ---@type Menu[] + Menus = {}, + ---@type Menu[] + ParentMenus = {}, + ---@type table + NUICallbacks = {}, + ---@type table + Translations = translations, + ---@class keys + Keys = setmetatable({ data = {}, __class = 'MenuVKeys', __type = 'keys' }, { + __index = function(t, k) + return rawget(t.data, k) + end, + __newindex = function(t, actionHax, v) + actionHax = Utilities:Ensure(actionHax, 'unknown') + + if (actionHax == 'unknown') then return end + + local rawKey = rawget(t.data, actionHax) + local keyExists = rawKey ~= nil + local prevState = Utilities:Ensure((rawKey or {}).status, false) + local newState = Utilities:Ensure(v, false) + + if (keyExists) then + rawset(t.data[actionHax], 'status', newState) + + if (prevState ~= newState and newState) then + rawKey.func(rawKey.menu) + end + end + end, + __call = function(t, actionHax, m, actionFunc, inputType) + actionHax = Utilities:Ensure(actionHax, 'unknown') + m = Utilities:Typeof(m) == 'Menu' and m or nil + actionFunc = Utilities:Ensure(actionFunc, function() end) + inputType = Utilities:Ensure(inputType, 'KEYBOARD') + inputType = upper(inputType) + + if (actionHax == 'unknown') then return end + + local rawKey = rawget(t.data, actionHax) + local keyExists = rawKey ~= nil + + if (keyExists) then + if not rawKey.inputTypes[inputType] then + rawKey.inputTypes[inputType] = true + end + + return + end + + rawset(t.data, actionHax, { status = false, menu = m, func = actionFunc, inputTypes = { [inputType] = true } }) + end + }) +} + +---@class MenuV +MenuV = setmetatable(menuv_table, {}) + +--- Send a NUI message to MenuV resource +---@param input any +local SEND_NUI_MESSAGE = function(input) + exports['menuv']:SendNUIMessage(input) +end + +--- Register a NUI callback event +---@param name string Name of callback +---@param cb function Callback to execute +local REGISTER_NUI_CALLBACK = function(name, cb) + name = Utilities:Ensure(name, 'unknown') + cb = Utilities:Ensure(cb, function(_, cb) cb('ok') end) + + MenuV.NUICallbacks[name] = cb +end + +--- Load translation +---@param k string Translation key +---@return string Translation or 'MISSING TRANSLATION' +function MenuV:T(k) + k = Utilities:Ensure(k, 'unknown') + + return Utilities:Ensure(MenuV.Translations[k], 'MISSING TRANSLATION') +end + +--- Create a `MenuV` menu +---@param title string Title of Menu +---@param subtitle string Subtitle of Menu +---@param position string Position of Menu +---@param r number 0-255 RED +---@param g number 0-255 GREEN +---@param b number 0-255 BLUE +---@param size string | "'size-100'" | "'size-110'" | "'size-125'" | "'size-150'" | "'size-175'" | "'size-200'" +---@param texture string Name of texture example: "default" +---@param dictionary string Name of dictionary example: "menuv" +---@param namespace string Namespace of Menu +---@param theme string Theme of Menu +---@return Menu +function MenuV:CreateMenu(title, subtitle, position, r, g, b, size, texture, dictionary, namespace, theme) + local menu = CreateMenu({ + Theme = theme, + Title = title, + Subtitle = subtitle, + Position = position, + R = r, + G = g, + B = b, + Size = size, + Texture = texture, + Dictionary = dictionary, + Namespace = namespace + }) + + local index = #(self.Menus or {}) + 1 + + insert(self.Menus, index, menu) + + return self.Menus[index] or menu +end + +--- Create a menu that inherits properties from another menu +---@param parent Menu|string Menu or UUID of menu +---@param overrides table Properties to override in menu object (ignore parent) +---@param namespace string Namespace of menu +function MenuV:InheritMenu(parent, overrides, namespace) + overrides = Utilities:Ensure(overrides, {}) + + local uuid = Utilities:Typeof(parent) == 'Menu' and parent.UUID or Utilities:Typeof(parent) == 'string' and parent + + if (uuid == nil) then return end + + local parentMenu = self:GetMenu(uuid) + + if (parentMenu == nil) then return end + + local menu = CreateMenu({ + Theme = Utilities:Ensure(overrides.theme or overrides.Theme, parentMenu.Theme), + Title = Utilities:Ensure(overrides.title or overrides.Title, parentMenu.Title), + Subtitle = Utilities:Ensure(overrides.subtitle or overrides.Subtitle, parentMenu.Subtitle), + Position = Utilities:Ensure(overrides.position or overrides.Position, parentMenu.Position), + R = Utilities:Ensure(overrides.r or overrides.R, parentMenu.Color.R), + G = Utilities:Ensure(overrides.g or overrides.G, parentMenu.Color.G), + B = Utilities:Ensure(overrides.b or overrides.B, parentMenu.Color.B), + Size = Utilities:Ensure(overrides.size or overrides.Size, parentMenu.Size), + Texture = Utilities:Ensure(overrides.texture or overrides.Texture, parentMenu.Texture), + Dictionary = Utilities:Ensure(overrides.dictionary or overrides.Dictionary, parentMenu.Dictionary), + Namespace = Utilities:Ensure(namespace, 'unknown') + }) + + local index = #(self.Menus or {}) + 1 + + insert(self.Menus, index, menu) + + return self.Menus[index] or menu +end + +--- Load a menu based on `uuid` +---@param uuid string UUID of menu +---@return Menu|nil Founded menu or `nil` +function MenuV:GetMenu(uuid) + uuid = Utilities:Ensure(uuid, '00000000-0000-0000-0000-000000000000') + + for _, v in pairs(self.Menus) do + if (v.UUID == uuid) then + return v + end + end + + return nil +end + +--- Open a menu +---@param menu Menu|string Menu or UUID of Menu +---@param cb function Execute this callback when menu has opened +function MenuV:OpenMenu(menu, cb, reopen) + local uuid = Utilities:Typeof(menu) == 'Menu' and menu.UUID or Utilities:Typeof(menu) == 'string' and menu + + if (uuid == nil) then return end + + cb = Utilities:Ensure(cb, function() end) + + menu = self:GetMenu(uuid) + + if (menu == nil) then return end + + local dictionaryLoaded = HAS_STREAMED_TEXTURE_DICT_LOADED(menu.Dictionary) + + if (not self.Loaded or not dictionaryLoaded) then + if (not dictionaryLoaded) then REQUEST_STREAMED_TEXTURE_DICT(menu.Dictionary) end + + CreateThread(function() + repeat Wait(0) until MenuV.Loaded + + if (not dictionaryLoaded) then + repeat Wait(10) until HAS_STREAMED_TEXTURE_DICT_LOADED(menu.Dictionary) + end + + MenuV:OpenMenu(uuid, cb) + end) + return + end + + if (self.CurrentMenu ~= nil) then + insert(self.ParentMenus, self.CurrentMenu) + + self.CurrentMenu:RemoveOnEvent('update', self.CurrentUpdateUUID) + self.CurrentMenu:DestroyThreads() + end + + self.CurrentMenu = menu + self.CurrentUpdateUUID = menu:On('update', function(m, k, v) + k = Utilities:Ensure(k, 'unknown') + + if (k == 'Title' or k == 'title') then + SEND_NUI_MESSAGE({ action = 'UPDATE_TITLE', title = Utilities:Ensure(v, 'MenuV'), __uuid = m.UUID }) + elseif (k == 'Subtitle' or k == 'subtitle') then + SEND_NUI_MESSAGE({ action = 'UPDATE_SUBTITLE', subtitle = Utilities:Ensure(v, ''), __uuid = m.UUID }) + elseif (k == 'Items' or k == 'items') then + SEND_NUI_MESSAGE({ action = 'UPDATE_ITEMS', items = (m.Items:ToTable() or {}), __uuid = m.UUID }) + elseif (k == 'Item' or k == 'item' and Utilities:Typeof(v) == 'Item') then + SEND_NUI_MESSAGE({ action = 'UPDATE_ITEM', item = m.Items:ItemToTable(v) or {}, __uuid = m.UUID }) + elseif (k == 'AddItem' or k == 'additem' and Utilities:Typeof(v) == 'Item') then + SEND_NUI_MESSAGE({ action = 'ADD_ITEM', item = m.Items:ItemToTable(v), __uuid = m.UUID }) + elseif (k == 'RemoveItem' or k == 'removeitem' and Utilities:Typeof(v) == 'Item') then + SEND_NUI_MESSAGE({ action = 'REMOVE_ITEM', uuid = v.UUID, __uuid = m.UUID }) + elseif (k == 'UpdateItem' or k == 'updateitem' and Utilities:Typeof(v) == 'Item') then + SEND_NUI_MESSAGE({ action = 'UPDATE_ITEM', item = m.Items:ItemToTable(v) or {}, __uuid = m.UUID }) + end + end) + + SEND_NUI_MESSAGE({ + action = 'OPEN_MENU', + menu = menu:ToTable(), + reopen = Utilities:Ensure(reopen, false) + }) + + cb() +end + +function MenuV:Refresh() + if (self.CurrentMenu == nil) then + return + end + + SEND_NUI_MESSAGE({ + action = 'REFRESH_MENU', + menu = self.CurrentMenu:ToTable() + }) +end + +--- Close a menu +---@param menu Menu|string Menu or UUID of Menu +---@param cb function Execute this callback when menu has is closed or parent menu has opened +function MenuV:CloseMenu(menu, cb) + local uuid = Utilities:Typeof(menu) == 'Menu' and menu.UUID or Utilities:Typeof(menu) == 'string' and menu + + if (uuid == nil) then cb() return end + + cb = Utilities:Ensure(cb, function() end) + menu = self:GetMenu(uuid) + + if (menu == nil or self.CurrentMenu == nil or self.CurrentMenu.UUID ~= uuid) then cb() return end + + self.CurrentMenu:RemoveOnEvent('update', self.CurrentUpdateUUID) + self.CurrentMenu:Trigger('close') + self.CurrentMenu:DestroyThreads() + self.CurrentMenu = nil + + SEND_NUI_MESSAGE({ action = 'CLOSE_MENU', uuid = uuid }) + + if (#self.ParentMenus <= 0) then cb() return end + + local prev_index = #self.ParentMenus + local prev_menu = self.ParentMenus[prev_index] or nil + + if (prev_menu == nil) then cb() return end + + remove(self.ParentMenus, prev_index) + + self:OpenMenu(prev_menu, function() + cb() + end, true) +end + +--- Close all menus +---@param cb function Execute this callback when all menus are closed +function MenuV:CloseAll(cb) + cb = Utilities:Ensure(cb, function() end) + + if (not self.Loaded) then + CreateThread(function() + repeat Wait(0) until MenuV.Loaded + + MenuV:CloseAll(cb) + end) + return + end + + if (self.CurrentMenu == nil) then cb() return end + + local uuid = Utilities:Ensure(self.CurrentMenu.UUID, '00000000-0000-0000-0000-000000000000') + + self.CurrentMenu:RemoveOnEvent('update', self.CurrentUpdateUUID) + self.CurrentMenu:Trigger('close') + self.CurrentMenu:DestroyThreads() + + SEND_NUI_MESSAGE({ action = 'CLOSE_MENU', uuid = uuid }) + + self.CurrentMenu = nil + self.ParentMenus = {} + + cb() +end + +--- Register keybind for specific menu +---@param menu Menu|string MenuV menu +---@param action string Name of action +---@param func function This will be executed +---@param description string Key description +---@param defaultType string Default key type +---@param defaultKey string Default key +function MenuV:AddControlKey(menu, action, func, description, defaultType, defaultKey) + local uuid = Utilities:Typeof(menu) == 'Menu' and menu.UUID or Utilities:Typeof(menu) == 'string' and menu + + action = Utilities:Ensure(action, 'UNKNOWN') + func = Utilities:Ensure(func, function() end) + description = Utilities:Ensure(description, 'unknown') + defaultType = Utilities:Ensure(defaultType, 'KEYBOARD') + defaultType = upper(defaultType) + defaultKey = Utilities:Ensure(defaultKey, 'F12') + + local m = self:GetMenu(uuid) + + if (m == nil) then return end + + if (Utilities:Typeof(m.Namespace) ~= 'string' or m.Namespace == 'unknown') then + error('[MenuV] Namespace is required for assigning keys.') + return + end + + action = Utilities:Replace(action, ' ', '_') + action = upper(action) + + local resourceName = Utilities:Ensure(self.CurrentResourceName, 'unknown') + local namespace = Utilities:Ensure(m.Namespace, 'unknown') + local actionHash = GET_HASH_KEY(('%s_%s_%s'):format(resourceName, namespace, action)) + local actionHax = format('%x', actionHash) + + local typeGroup = Utilities:GetInputTypeGroup(defaultType) + + if (self.Keys[actionHax] and self.Keys[actionHax].inputTypes[typeGroup]) then return end + + self.Keys(actionHax, m, func, typeGroup) + + local k = actionHax + + if typeGroup > 0 then + local inputGroupName = Utilities:GetInputGroupName(typeGroup) + k = ('%s_%s'):format(lower(inputGroupName), k) + end + + REGISTER_KEY_MAPPING(('+%s'):format(k), description, defaultType, defaultKey) + REGISTER_COMMAND(('+%s'):format(k), function() MenuV.Keys[actionHax] = true end) + REGISTER_COMMAND(('-%s'):format(k), function() MenuV.Keys[actionHax] = false end) +end + +--- Checks if namespace is available +---@param namespace string Namespace +---@return boolean Returns `true` if given namespace is available +function MenuV:IsNamespaceAvailable(namespace) + namespace = lower(Utilities:Ensure(namespace, 'unknown')) + + if (namespace == 'unknown') then return true end + + ---@param v Menu + for k, v in pairs(self.Menus or {}) do + local v_namespace = Utilities:Ensure(v.Namespace, 'unknown') + + if (namespace == lower(v_namespace)) then + return false + end + end + + return true +end + + +--- Mark MenuV as loaded when `main` resource is loaded +exports['menuv']:IsLoaded(function() + MenuV.Loaded = true +end) + +--- Register callback handler for MenuV +exports('NUICallback', function(name, info, cb) + name = Utilities:Ensure(name, 'unknown') + + if (MenuV.NUICallbacks == nil or MenuV.NUICallbacks[name] == nil) then + return + end + + MenuV.NUICallbacks[name](info, cb) +end) + +REGISTER_NUI_CALLBACK('open', function(info, cb) + local uuid = Utilities:Ensure(info.uuid, '00000000-0000-0000-0000-000000000000') + local new_uuid = Utilities:Ensure(info.new_uuid, '00000000-0000-0000-0000-000000000000') + + cb('ok') + + if (MenuV.CurrentMenu == nil or MenuV.CurrentMenu.UUID == uuid or MenuV.CurrentMenu.UUID == new_uuid) then return end + + for _, v in pairs(MenuV.ParentMenus) do + if (v.UUID == uuid) then + return + end + end + + MenuV.CurrentMenu:RemoveOnEvent('update', MenuV.CurrentUpdateUUID) + MenuV.CurrentMenu:Trigger('close') + MenuV.CurrentMenu:DestroyThreads() + MenuV.CurrentMenu = nil + MenuV.ParentMenus = {} +end) + +REGISTER_NUI_CALLBACK('opened', function(info, cb) + local uuid = Utilities:Ensure(info.uuid, '00000000-0000-0000-0000-000000000000') + + cb('ok') + + if (MenuV.CurrentMenu == nil or MenuV.CurrentMenu.UUID ~= uuid) then return end + + MenuV.CurrentMenu:Trigger('open') +end) + +REGISTER_NUI_CALLBACK('submit', function(info, cb) + local uuid = Utilities:Ensure(info.uuid, '00000000-0000-0000-0000-000000000000') + + cb('ok') + + if (MenuV.CurrentMenu == nil) then return end + + for k, v in pairs(MenuV.CurrentMenu.Items) do + if (v.UUID == uuid) then + if (v.__type == 'confirm' or v.__type == 'checkbox') then + v.Value = Utilities:Ensure(info.value, false) + elseif (v.__type == 'range') then + v.Value = Utilities:Ensure(info.value, v.Min) + elseif (v.__type == 'slider') then + v.Value = Utilities:Ensure(info.value, 0) + 1 + end + + MenuV.CurrentMenu:Trigger('select', v) + + if (v.__type == 'button' or v.__type == 'menu') then + MenuV.CurrentMenu.Items[k]:Trigger('select') + elseif (v.__type == 'range') then + MenuV.CurrentMenu.Items[k]:Trigger('select', v.Value) + elseif (v.__type == 'slider') then + local option = MenuV.CurrentMenu.Items[k].Values[v.Value] or nil + + if (option == nil) then return end + + MenuV.CurrentMenu.Items[k]:Trigger('select', option.Value) + end + + return + end + end +end) + +REGISTER_NUI_CALLBACK('close', function(info, cb) + local uuid = Utilities:Ensure(info.uuid, '00000000-0000-0000-0000-000000000000') + + if (MenuV.CurrentMenu == nil or MenuV.CurrentMenu.UUID ~= uuid) then cb('ok') return end + + MenuV.CurrentMenu:RemoveOnEvent('update', MenuV.CurrentUpdateUUID) + MenuV.CurrentMenu:Trigger('close') + MenuV.CurrentMenu:DestroyThreads() + MenuV.CurrentMenu = nil + + if (#MenuV.ParentMenus <= 0) then cb('ok') return end + + local prev_index = #MenuV.ParentMenus + local prev_menu = MenuV.ParentMenus[prev_index] or nil + + if (prev_menu == nil) then cb('ok') return end + + remove(MenuV.ParentMenus, prev_index) + + MenuV:OpenMenu(prev_menu, function() + cb('ok') + end, true) +end) + +REGISTER_NUI_CALLBACK('close_all', function(info, cb) + if (MenuV.CurrentMenu == nil) then cb('ok') return end + + MenuV.CurrentMenu:RemoveOnEvent('update', MenuV.CurrentUpdateUUID) + MenuV.CurrentMenu:Trigger('close') + MenuV.CurrentMenu:DestroyThreads() + MenuV.CurrentMenu = nil + MenuV.ParentMenus = {} + + cb('ok') +end) + +REGISTER_NUI_CALLBACK('switch', function(info, cb) + local prev_uuid = Utilities:Ensure(info.prev, '00000000-0000-0000-0000-000000000000') + local next_uuid = Utilities:Ensure(info.next, '00000000-0000-0000-0000-000000000000') + local prev_item, next_item = nil, nil + + cb('ok') + + if (MenuV.CurrentMenu == nil) then return end + + for k, v in pairs(MenuV.CurrentMenu.Items) do + if (v.UUID == prev_uuid) then + prev_item = v + + MenuV.CurrentMenu.Items[k]:Trigger('leave') + end + + if (v.UUID == next_uuid) then + next_item = v + + MenuV.CurrentMenu.Items[k]:Trigger('enter') + end + end + + if (prev_item ~= nil and next_item ~= nil) then + MenuV.CurrentMenu:Trigger('switch', next_item, prev_item) + end +end) + +REGISTER_NUI_CALLBACK('update', function(info, cb) + local uuid = Utilities:Ensure(info.uuid, '00000000-0000-0000-0000-000000000000') + + cb('ok') + + if (MenuV.CurrentMenu == nil) then return end + + for k, v in pairs(MenuV.CurrentMenu.Items) do + if (v.UUID == uuid) then + local newValue, oldValue = nil, nil + + if (v.__type == 'confirm' or v.__type == 'checkbox') then + newValue = Utilities:Ensure(info.now, false) + oldValue = Utilities:Ensure(info.prev, false) + elseif (v.__type == 'range') then + newValue = Utilities:Ensure(info.now, v.Min) + oldValue = Utilities:Ensure(info.prev, v.Min) + elseif (v.__type == 'slider') then + newValue = Utilities:Ensure(info.now, 0) + 1 + oldValue = Utilities:Ensure(info.prev, 0) + 1 + end + + if (Utilities:Any(v.__type, { 'button', 'menu', 'label' }, 'value')) then return end + + MenuV.CurrentMenu:Trigger('update', v, newValue, oldValue) + MenuV.CurrentMenu.Items[k]:Trigger('change', newValue, oldValue) + + if (v.SaveOnUpdate) then + MenuV.CurrentMenu.Items[k].Value = newValue + + if (v.__type == 'range') then + MenuV.CurrentMenu.Items[k]:Trigger('select', v.Value) + elseif (v.__type == 'slider') then + local option = MenuV.CurrentMenu.Items[k].Values[v.Value] or nil + + if (option == nil) then return end + + MenuV.CurrentMenu.Items[k]:Trigger('select', option.Value) + end + end + return + end + end +end) diff --git a/resources/[standalone]/menuv/menuv/components/item.lua b/resources/[standalone]/menuv/menuv/components/item.lua new file mode 100644 index 0000000..5cedbe8 --- /dev/null +++ b/resources/[standalone]/menuv/menuv/components/item.lua @@ -0,0 +1,273 @@ +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +local assert = assert +---@type Utilities +local U = assert(Utilities) +local type = assert(type) +local pairs = assert(pairs) +local lower = assert(string.lower) +local upper = assert(string.upper) +local sub = assert(string.sub) +local pack = assert(table.pack) +local unpack = assert(table.unpack) +local insert = assert(table.insert) +local rawset = assert(rawset) +local rawget = assert(rawget) +local setmetatable = assert(setmetatable) + +--- FiveM globals +local CreateThread = assert(Citizen.CreateThread) + +--- Create a new menu item +---@param info table Menu information +---@return Item New item +function CreateMenuItem(info) + info = U:Ensure(info, {}) + + local item = { + ---@type Menu|nil + __menu = U:Ensure(info.__Menu or info.__menu, { __class = 'Menu', __type = 'Menu' }, true) or nil, + ---@type string + __event = U:Ensure(info.PrimaryEvent or info.primaryEvent, 'unknown'), + ---@type string + UUID = U:UUID(), + ---@type string + Icon = U:Ensure(info.Icon or info.icon, 'none'), + ---@type string + Label = U:Ensure(info.Label or info.label, ''), + ---@type string + Description = U:Ensure(info.Description or info.description, ''), + ---@type any + Value = info.Value or info.value, + ---@type table[] + Values = {}, + ---@type number + Min = U:Ensure(info.Min or info.min, 0), + ---@type number + Max = U:Ensure(info.Max or info.max, 0), + ---@type boolean + Disabled = U:Ensure(info.Disabled or info.disabled, false), + ---@type table + Events = U:Ensure(info.Events or info.events, {}), + ---@type boolean + SaveOnUpdate = U:Ensure(info.SaveOnUpdate or info.saveOnUpdate, false), + ---@param t Item + ---@param event string Name of Event + Trigger = function(t, event, ...) + event = lower(U:Ensure(event, 'unknown')) + + if (event == 'unknown') then return end + if (U:StartsWith(event, 'on')) then + event = 'On' .. sub(event, 3):gsub('^%l', upper) + else + event = 'On' .. event:gsub('^%l', upper) + end + + if (not U:Any(event, (t.Events or {}), 'key')) then + return + end + + local args = pack(...) + + for _, v in pairs(t.Events[event]) do + CreateThread(function() + v(t, unpack(args)) + end) + end + end, + ---@param t Item + ---@param event string Name of event + ---@param func function|Menu Function or Menu to trigger + On = function(t, event, func) + event = lower(U:Ensure(event, 'unknown')) + + if (event == 'unknown') then return end + if (U:StartsWith(event, 'on')) then + event = 'On' .. sub(event, 3):gsub('^%l', upper) + else + event = 'On' .. event:gsub('^%l', upper) + end + + if (not U:Any(event, (t.Events or {}), 'key')) then + return + end + + local _type = U:Typeof(func) + + if (_type == 'Menu') then + local menu_t = { + __class = 'function', + __type = 'function', + func = function(t) MenuV:OpenMenu(t.uuid) end, + uuid = func.UUID or func.uuid or U:UUID() + } + local menu_mt = { __index = menu_t, __call = function(t) t:func() end } + local menu_item = setmetatable(menu_t, menu_mt) + + insert(t.Events[event], menu_item) + + return + end + + func = U:Ensure(func, function() end) + + insert(t.Events[event], func) + end, + ---@param t Item + ---@param k string + ---@param v string + Validate = U:Ensure(info.Validate or info.validate, function(t, k, v) + return true + end), + ---@param t Item + ---@param k string + ---@param v string + Parser = U:Ensure(info.Parser or info.parser, function(t, k, v) + return v + end), + ---@param t Item + ---@param k string + ---@param v string + NewIndex = U:Ensure(info.NewIndex or info.newIndex, function(t, k, v) + end), + ---@param t Item + ---@return any + GetValue = function(t) + local itemType = U:Ensure(t.__type, 'unknown') + + if (itemType == 'button' or itemType == 'menu' or itemType == 'unknown') then + return t.Value + end + + if (itemType == 'checkbox' or itemType == 'confirm') then + return U:Ensure(t.Value, false) + end + + if (itemType == 'slider') then + for _, item in pairs(t.Values) do + if (item.Value == t.Value) then + return item.Value + end + end + + return nil + end + + if (itemType == 'range') then + local rawValue = U:Ensure(t.Value, 0) + + if (t.Min > rawValue) then + return t.Min + end + + if (t.Max < rawValue) then + return t.Max + end + + return rawValue + end + end, + ---@return Menu|nil + GetParentMenu = function(t) + return t.__menu or nil + end + } + + item.Events.OnEnter = {} + item.Events.OnLeave = {} + item.Events.OnUpdate = {} + item.Events.OnDestroy = {} + + local mt = { + __index = function(t, k) + return rawget(t.data, k) + end, + __tostring = function(t) + return t.UUID + end, + __call = function(t, ...) + if (t.Trigger ~= nil and type(t.Trigger) == 'function') then + t:Trigger(t.__event, ...) + end + end, + __newindex = function(t, k, v) + local key = U:Ensure(k, 'unknown') + local oldValue = rawget(t.data, k) + local checkInput = t.Validate ~= nil and type(t.Validate) == 'function' + local inputParser = t.Parser ~= nil and type(t.Parser) == 'function' + local updateIndexTrigger = t.NewIndex ~= nil and type(t.NewIndex) == 'function' + + if (checkInput) then + local result = t:Validate(key, v) + result = U:Ensure(result, true) + + if (not result) then + return + end + end + + if (inputParser) then + local parsedValue = t:Parser(key, v) + + v = parsedValue or v + end + + rawset(t.data, k, v) + + if (updateIndexTrigger) then + t:NewIndex(key, v) + end + + if (t.__menu ~= nil and U:Typeof(t.__menu) == 'Menu' and t.__menu.Trigger ~= nil and U:Typeof( t.__menu.Trigger) == 'function') then + t.__menu:Trigger('update', 'UpdateItem', t) + end + + if (key == 'Value' and t.Trigger ~= nil and type(t.Trigger) == 'function') then + t:Trigger('update', key, v, oldValue) + end + end, + __metatable = 'MenuV' + } + + ---@class Item + ---@filed private __event string Name of primary event + ---@field public UUID string UUID of Item + ---@field public Icon string Icon/Emoji for Item + ---@field public Label string Label of Item + ---@field public Description string Description of Item + ---@field public Value any Value of Item + ---@field public Values table[] List of values + ---@field public Min number Min range value + ---@field public Max number Max range value + ---@field public Disabled boolean Disabled state of Item + ---@field public SaveOnUpdate boolean Save on `update` + ---@field private Events table List of registered `on` events + ---@field public Trigger fun(t: Item, event: string) + ---@field public On fun(t: Item, event: string, func: function|Menu) + ---@field public Validate fun(t: Item, k: string, v:any) + ---@field public NewIndex fun(t: Item, k: string, v: any) + ---@field public Parser fun(t: Item, k: string, v: any) + ---@field public GetValue fun(t: Item):any + ---@field public GetParentMenu func(t: Item):Menu|nil + local i = setmetatable({ data = item, __class = 'Item', __type = U:Ensure(info.Type or info.type, 'unknown') }, mt) + + for k, v in pairs(info or {}) do + local key = U:Ensure(k, 'unknown') + + if (key == 'unknown') then return end + + i:On(key, v) + end + + return i +end + +_ENV.CreateMenuItem = CreateMenuItem +_G.CreateMenuItem = CreateMenuItem \ No newline at end of file diff --git a/resources/[standalone]/menuv/menuv/components/menu.lua b/resources/[standalone]/menuv/menuv/components/menu.lua new file mode 100644 index 0000000..a8b3b31 --- /dev/null +++ b/resources/[standalone]/menuv/menuv/components/menu.lua @@ -0,0 +1,1080 @@ +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +local assert = assert +---@type Utilities +local U = assert(Utilities) +local type = assert(type) +local next = assert(next) +local pairs = assert(pairs) +local ipairs = assert(ipairs) +local lower = assert(string.lower) +local upper = assert(string.upper) +local sub = assert(string.sub) +local insert = assert(table.insert) +local remove = assert(table.remove) +local pack = assert(table.pack) +local unpack = assert(table.unpack) +local encode = assert(json.encode) +local rawset = assert(rawset) +local rawget = assert(rawget) +local setmetatable = assert(setmetatable) + +--- FiveM globals +local GET_CURRENT_RESOURCE_NAME = assert(GetCurrentResourceName) +local GET_INVOKING_RESOURCE = assert(GetInvokingResource) +local HAS_STREAMED_TEXTURE_DICT_LOADED = assert(HasStreamedTextureDictLoaded) +local REQUEST_STREAMED_TEXTURE_DICT = assert(RequestStreamedTextureDict) + +--- MenuV local variable +local current_resource = GET_CURRENT_RESOURCE_NAME() + +--- Returns default empty table for items +---@returns items +function CreateEmptyItemsTable(data) + data = U:Ensure(data, {}) + data.ToTable = function(t) + local tempTable = {} + local index = 0 + + for _, option in pairs(t) do + index = index + 1 + + tempTable[index] = { + index = index, + type = option.__type, + uuid = U:Ensure(option.UUID, 'unknown'), + icon = U:Ensure(option.Icon, 'none'), + label = U:Ensure(option.Label, 'Unknown'), + description = U:Ensure(option.Description, ''), + value = 'none', + values = {}, + min = U:Ensure(option.Min, 0), + max = U:Ensure(option.Max, 0), + disabled = U:Ensure(option.Disabled, false) + } + + if (option.__type == 'button' or option.__type == 'menu') then + tempTable[index].value = 'none' + elseif (option.__type == 'checkbox' or option.__type == 'confirm') then + tempTable[index].value = U:Ensure(option.Value, false) + elseif (option.__type == 'range') then + tempTable[index].value = U:Ensure(option.Value, 0) + + if (tempTable[index].value <= tempTable[index].min) then + tempTable[index].value = tempTable[index].min + elseif (tempTable[index].value >= tempTable[index].max) then + tempTable[index].value = tempTable[index].max + end + elseif (option.__type == 'slider') then + tempTable[index].value = 0 + end + + local _values = U:Ensure(option.Values, {}) + local vIndex = 0 + + for valueIndex, value in pairs(_values) do + vIndex = vIndex + 1 + + tempTable[index].values[vIndex] = { + label = U:Ensure(value.Label, 'Option'), + description = U:Ensure(value.Description, ''), + value = vIndex + } + + if (option.__type == 'slider') then + if (U:Ensure(option.Value, 0) == valueIndex) then + tempTable[index].value = (valueIndex - 1) + end + end + end + end + + return tempTable + end + data.ItemToTable = function(t, i) + local tempTable = {} + local index = 0 + local uuid = U:Typeof(i) == 'Item' and i.UUID or U:Ensure(i, '00000000-0000-0000-0000-000000000000') + + for _, option in pairs(t) do + index = index + 1 + + if (option.UUID == uuid) then + tempTable = { + index = index, + type = option.__type, + uuid = U:Ensure(option.UUID, 'unknown'), + icon = U:Ensure(option.Icon, 'none'), + label = U:Ensure(option.Label, 'Unknown'), + description = U:Ensure(option.Description, ''), + value = 'none', + values = {}, + min = U:Ensure(option.Min, 0), + max = U:Ensure(option.Max, 0), + disabled = U:Ensure(option.Disabled, false) + } + + if (option.__type == 'button' or option.__type == 'menu') then + tempTable.value = 'none' + elseif (option.__type == 'checkbox' or option.__type == 'confirm') then + tempTable.value = U:Ensure(option.Value, false) + elseif (option.__type == 'range') then + tempTable.value = U:Ensure(option.Value, 0) + + if (tempTable.value <= tempTable.min) then + tempTable.value = tempTable.min + elseif (tempTable.value >= tempTable.max) then + tempTable.value = tempTable.max + end + elseif (option.__type == 'slider') then + tempTable.value = 0 + end + + local _values = U:Ensure(option.Values, {}) + local vIndex = 0 + + for valueIndex, value in pairs(_values) do + vIndex = vIndex + 1 + + tempTable.values[vIndex] = { + label = U:Ensure(value.Label, 'Option'), + description = U:Ensure(value.Description, ''), + value = vIndex + } + + if (option.__type == 'slider') then + if (U:Ensure(option.Value, 0) == valueIndex) then + tempTable.value = (valueIndex - 1) + end + end + end + + return tempTable + end + end + + return tempTable + end + data.AddItem = function(t, item) + if (U:Typeof(item) == 'Item') then + local newIndex = #(U:Ensure(rawget(t, 'data'), {})) + 1 + + rawset(t.data, newIndex, item) + + if (t.Trigger ~= nil and type(t.Trigger) == 'function') then + t:Trigger('update', 'AddItem', item) + end + end + + return U:Ensure(rawget(t, 'data'), {}) + end + + local item_pairs = function(t, k) + local _k, _v = next((rawget(t, 'data') or {}), k) + + if (_v ~= nil and type(_v) ~= 'table') then + return item_pairs(t, _k) + end + + return _k, _v + end + + local item_ipairs = function(t, k) + local _k, _v = next((rawget(t, 'data') or {}), k) + + if (_v ~= nil and (type(_v) ~= 'table' or type(_k) ~= 'number')) then + return item_ipairs(t, _k) + end + + return _k, _v + end + + _G.item_pairs = item_pairs + _ENV.item_pairs = item_pairs + + ---@class items + return setmetatable({ data = data, Trigger = nil }, { + __index = function(t, k) + return rawget(t.data, k) + end, + __newindex = function(t, k, v) + local oldValue = rawget(t.data, k) + + rawset(t.data, k, v) + + if (t.Trigger ~= nil and type(t.Trigger) == 'function') then + if (oldValue == nil) then + t:Trigger('update', 'AddItem', v) + elseif (oldValue ~= nil and v == nil) then + t:Trigger('update', 'RemoveItem', oldValue) + elseif (oldValue ~= v) then + t:Trigger('update', 'UpdateItem', v, oldValue) + end + end + end, + __call = function(t, func) + rawset(t, 'Trigger', U:Ensure(func, function() end)) + end, + __pairs = function(t) + local k = nil + + return function() + local v + + k, v = item_pairs(t, k) + + return k, v + end, t, nil + end, + __ipairs = function(t) + local k = nil + + return function() + local v + + k, v = item_ipairs(t, k) + + return k, v + end, t, 0 + end, + __len = function(t) + local items = U:Ensure(rawget(t, 'data'), {}) + local itemCount = 0 + + for _, v in pairs(items) do + if (U:Typeof(v) == 'Item') then + itemCount = itemCount + 1 + end + end + + return itemCount + end + }) +end + +--- Load a texture dictionary if not already loaded +---@param textureDictionary string Name of texture dictionary +local function LoadTextureDictionary(textureDictionary) + textureDictionary = U:Ensure(textureDictionary, 'menuv') + + if (HAS_STREAMED_TEXTURE_DICT_LOADED(textureDictionary)) then return end + + REQUEST_STREAMED_TEXTURE_DICT(textureDictionary, true) +end + +--- Create a new menu item +---@param info table Menu information +---@return Menu New item +function CreateMenu(info) + info = U:Ensure(info, {}) + + local namespace = U:Ensure(info.Namespace or info.namespace, 'unknown') + local namespace_available = MenuV:IsNamespaceAvailable(namespace) + + if (not namespace_available) then + error(("[MenuV] Namespace '%s' is already taken, make sure it is unique."):format(namespace)) + end + + local theme = lower(U:Ensure(info.Theme or info.theme, 'default')) + + if (theme ~= 'default' and theme ~= 'native') then + theme = 'default' + end + + if (theme == 'native') then + info.R, info.G, info.B = 255, 255, 255 + info.r, info.g, info.b = 255, 255, 255 + end + + local item = { + ---@type string + Namespace = namespace, + ---@type boolean + IsOpen = false, + ---@type string + UUID = U:UUID(), + ---@type string + Title = not (info.Title or info.title) and '‏‏‎ ‎' or U:Ensure(info.Title or info.title, 'MenuV'), + ---@type string + Subtitle = U:Ensure(info.Subtitle or info.subtitle, ''), + ---@type string | "'topleft'" | "'topcenter'" | "'topright'" | "'centerleft'" | "'center'" | "'centerright'" | "'bottomleft'" | "'bottomcenter'" | "'bottomright'" + Position = U:Ensure(info.Position or info.position, 'topleft'), + ---@type table + Color = { + R = U:Ensure(info.R or info.r, 0), + G = U:Ensure(info.G or info.g, 0), + B = U:Ensure(info.B or info.b, 255) + }, + ---@type string | "'size-100'" | "'size-110'" | "'size-125'" | "'size-150'" | "'size-175'" | "'size-200'" + Size = U:Ensure(info.Size or info.size, 'size-110'), + ---@type string + Dictionary = U:Ensure(info.Dictionary or info.dictionary, 'menuv'), + ---@type string + Texture = U:Ensure(info.Texture or info.texture, 'default'), + ---@type table + Events = U:Ensure(info.Events or info.events, {}), + ---@type string + Theme = theme, + ---@type Item[] + Items = CreateEmptyItemsTable({}), + ---@param t Menu + ---@param event string Name of Event + Trigger = function(t, event, ...) + event = lower(U:Ensure(event, 'unknown')) + + if (event == 'unknown') then return end + if (U:StartsWith(event, 'on')) then + event = 'On' .. sub(event, 3):gsub('^%l', upper) + else + event = 'On' .. event:gsub('^%l', upper) + end + + if (not U:Any(event, (t.Events or {}), 'key')) then + return + end + + if (event == 'OnOpen') then rawset(t, 'IsOpen', true) + elseif (event == 'OnClose') then rawset(t, 'IsOpen', false) end + + local args = pack(...) + + for _, v in pairs(t.Events[event]) do + if (type(v) == 'table' and U:Typeof(v.func) == 'function') then + CreateThread(function() + if (event == 'OnClose') then + v.func(t, unpack(args)) + else + local threadId = coroutine.running() + + if (threadId ~= nil) then + insert(t.data.Threads, threadId) + + v.func(t, unpack(args)) + + for i = 0, #(t.data.Threads or {}), 1 do + if (t.data.Threads[i] == threadId) then + remove(t.data.Threads, i) + return + end + end + end + end + end) + end + end + end, + ---@type thread[] + Threads = {}, + ---@param t Menu + DestroyThreads = function(t) + for _, threadId in pairs(t.data.Threads or {}) do + local threadStatus = coroutine.status(threadId) + + if (threadStatus ~= nil and threadStatus ~= 'dead') then + coroutine.close(threadId) + end + end + + t.data.Threads = {} + end, + ---@param t Menu + ---@param event string Name of event + ---@param func function|Menu Function or Menu to trigger + ---@return string UUID of event + On = function(t, event, func) + local ir = GET_INVOKING_RESOURCE() + local resource = U:Ensure(ir, current_resource) + + event = lower(U:Ensure(event, 'unknown')) + + if (event == 'unknown') then return end + if (U:StartsWith(event, 'on')) then + event = 'On' .. sub(event, 3):gsub('^%l', upper) + else + event = 'On' .. event:gsub('^%l', upper) + end + + if (not U:Any(event, (t.Events or {}), 'key')) then + return + end + + func = U:Ensure(func, function() end) + + local uuid = U:UUID() + + insert(t.Events[event], { + __uuid = uuid, + __resource = resource, + func = func + }) + + return uuid + end, + ---@param t Menu + ---@param event string Name of event + ---@param uuid string UUID of event + RemoveOnEvent = function(t, event, uuid) + local ir = GET_INVOKING_RESOURCE() + local resource = U:Ensure(ir, current_resource) + + event = lower(U:Ensure(event, 'unknown')) + + if (event == 'unknown') then return end + if (U:StartsWith(event, 'on')) then + event = 'On' .. sub(event, 3):gsub('^%l', upper) + else + event = 'On' .. event:gsub('^%l', upper) + end + + if (not U:Any(event, (t.Events or {}), 'key')) then + return + end + + uuid = U:Ensure(uuid, '00000000-0000-0000-0000-000000000000') + + for i = 1, #t.Events[event], 1 do + if (t.Events[event][i] ~= nil and + t.Events[event][i].__uuid == uuid and + t.Events[event][i].__resource == resource) then + remove(t.Events[event], i) + end + end + end, + ---@param t Item + ---@param k string + ---@param v string + Validate = U:Ensure(info.Validate or info.validate, function(t, k, v) + return true + end), + ---@param t Item + ---@param k string + ---@param v string + Parser = function(t, k, v) + if (k == 'Position' or k == 'position') then + local position = lower(U:Ensure(v, 'topleft')) + + if (U:Any(position, {'topleft', 'topcenter', 'topright', 'centerleft', 'center', 'centerright', 'bottomleft', 'bottomcenter', 'bottomright'}, 'value')) then + return position + else + return 'topleft' + end + end + + return v + end, + ---@param t Item + ---@param k string + ---@param v string + NewIndex = U:Ensure(info.NewIndex or info.newIndex, function(t, k, v) + end), + ---@type function + ---@param t Menu MenuV menu + ---@param info table Information about button + ---@return Item New item + AddButton = function(t, info) + info = U:Ensure(info, {}) + + info.Type = 'button' + info.Events = { OnSelect = {} } + info.PrimaryEvent = 'OnSelect' + info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false) + info.__menu = t + + if (U:Typeof(info.Value or info.value) == 'Menu') then + info.Type = 'menu' + end + + local item = CreateMenuItem(info) + + if (info.Type == 'menu') then + item:On('select', function() item.Value() end) + end + + if (info.TriggerUpdate) then + t.Items:AddItem(item) + else + local items = rawget(t.data, 'Items') + + if (items) then + local newIndex = #items + 1 + + rawset(items.data, newIndex, item) + + return items.data[newIndex] or item + end + end + + return t.Items[#t.Items] or item + end, + ---@type function + ---@param t Menu MenuV menu + ---@param info table Information about checkbox + ---@return Item New item + AddCheckbox = function(t, info) + info = U:Ensure(info, {}) + + info.Type = 'checkbox' + info.Value = U:Ensure(info.Value or info.value, false) + info.Events = { OnChange = {}, OnCheck = {}, OnUncheck = {} } + info.PrimaryEvent = 'OnCheck' + info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false) + info.__menu = t + info.NewIndex = function(t, k, v) + if (k == 'Value') then + local value = U:Ensure(v, false) + + if (value) then + t:Trigger('check', t) + else + t:Trigger('uncheck', t) + end + end + end + + local item = CreateMenuItem(info) + + if (info.TriggerUpdate) then + t.Items:AddItem(item) + else + local items = rawget(t.data, 'Items') + + if (items) then + local newIndex = #items + 1 + + rawset(items.data, newIndex, item) + + return items.data[newIndex] or item + end + end + + return t.Items[#t.Items] or item + end, + ---@type function + ---@param t Menu MenuV menu + ---@param info table Information about slider + ---@return SliderItem New slider item + AddSlider = function(t, info) + info = U:Ensure(info, {}) + + info.Type = 'slider' + info.Events = { OnChange = {}, OnSelect = {} } + info.PrimaryEvent = 'OnSelect' + info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false) + info.__menu = t + + ---@class SliderItem : Item + ---@filed private __event string Name of primary event + ---@field public UUID string UUID of Item + ---@field public Icon string Icon/Emoji for Item + ---@field public Label string Label of Item + ---@field public Description string Description of Item + ---@field public Value any Value of Item + ---@field public Values table[] List of values + ---@field public Min number Min range value + ---@field public Max number Max range value + ---@field public Disabled boolean Disabled state of Item + ---@field private Events table List of registered `on` events + ---@field public Trigger fun(t: Item, event: string) + ---@field public On fun(t: Item, event: string, func: function) + ---@field public Validate fun(t: Item, k: string, v:any) + ---@field public NewIndex fun(t: Item, k: string, v: any) + ---@field public GetValue fun(t: Item):any + ---@field public AddValue fun(t: Item, info: table) + ---@field public AddValues fun(t: Item) + local item = CreateMenuItem(info) + + --- Add a value to slider + ---@param info table Information about slider + function item:AddValue(info) + info = U:Ensure(info, {}) + + local value = { + Label = U:Ensure(info.Label or info.label, 'Value'), + Description = U:Ensure(info.Description or info.description, ''), + Value = info.Value or info.value + } + + insert(self.Values, value) + end + + --- Add values to slider + ---@vararg table[] List of values + function item:AddValues(...) + local arguments = pack(...) + + for _, argument in pairs(arguments) do + if (U:Typeof(argument) == 'table') then + local hasIndex = argument[1] or nil + + if (hasIndex and U:Typeof(hasIndex) == 'table') then + self:AddValues(unpack(argument)) + else + self:AddValue(argument) + end + end + end + end + + local values = U:Ensure(info.Values or info.values, {}) + + if (#values > 0) then + item:AddValues(values) + end + + if (info.TriggerUpdate) then + t.Items:AddItem(item) + else + local items = rawget(t.data, 'Items') + + if (items) then + local newIndex = #items + 1 + + rawset(items.data, newIndex, item) + + return items.data[newIndex] or item + end + end + + return t.Items[#t.Items] or item + end, + ---@type function + ---@param t Menu MenuV menu + ---@param info table Information about range + ---@return RangeItem New Range item + AddRange = function(t, info) + info = U:Ensure(info, {}) + + info.Type = 'range' + info.Events = { OnChange = {}, OnSelect = {}, OnMin = {}, OnMax = {} } + info.PrimaryEvent = 'OnSelect' + info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false) + info.__menu = t + info.Value = U:Ensure(info.Value or info.value, 0) + info.Min = U:Ensure(info.Min or info.min, 0) + info.Max = U:Ensure(info.Max or info.max, 0) + info.Validate = function(t, k, v) + if (k == 'Value' or k == 'value') then + v = U:Ensure(v, 0) + + if (t.Min > v) then return false end + if (t.Max < v) then return false end + end + + return true + end + + if (info.Min > info.Max) then + local min = info.Min + local max = info.Max + + info.Min = min + info.Max = max + end + + if (info.Value < info.Min) then info.Value = info.Min end + if (info.Value > info.Max) then info.Value = info.Max end + + ---@class RangeItem : Item + ---@filed private __event string Name of primary event + ---@field public UUID string UUID of Item + ---@field public Icon string Icon/Emoji for Item + ---@field public Label string Label of Item + ---@field public Description string Description of Item + ---@field public Value any Value of Item + ---@field public Values table[] List of values + ---@field public Min number Min range value + ---@field public Max number Max range value + ---@field public Disabled boolean Disabled state of Item + ---@field private Events table List of registered `on` events + ---@field public Trigger fun(t: Item, event: string) + ---@field public On fun(t: Item, event: string, func: function) + ---@field public Validate fun(t: Item, k: string, v:any) + ---@field public NewIndex fun(t: Item, k: string, v: any) + ---@field public GetValue fun(t: Item):any + ---@field public SetMinValue fun(t: any) + ---@field public SetMaxValue fun(t: any) + local item = CreateMenuItem(info) + + --- Update min value of range + ---@param input number Minimum value of Range + function item:SetMinValue(input) + input = U:Ensure(input, 0) + + self.Min = input + + if (self.Value < self.Min) then + self.Value = self.Min + end + + if (self.Min > self.Max) then + self.Max = self.Min + end + end + + --- Update max value of range + ---@param input number Minimum value of Range + function item:SetMaxValue(input) + input = U:Ensure(input, 0) + + self.Min = input + + if (self.Value > self.Max) then + self.Value = self.Max + end + + if (self.Min < self.Max) then + self.Min = self.Max + end + end + + if (info.TriggerUpdate) then + t.Items:AddItem(item) + else + local items = rawget(t.data, 'Items') + + if (items) then + local newIndex = #items + 1 + + rawset(items.data, newIndex, item) + + return items.data[newIndex] or item + end + end + + return t.Items[#t.Items] or item + end, + ---@type function + ---@param t Menu MenuV menu + ---@param info table Information about confirm + ---@return ConfirmItem New Confirm item + AddConfirm = function(t, info) + info = U:Ensure(info, {}) + + info.Type = 'confirm' + info.Value = U:Ensure(info.Value or info.value, false) + info.Events = { OnConfirm = {}, OnDeny = {}, OnChange = {} } + info.PrimaryEvent = 'OnConfirm' + info.TriggerUpdate = not U:Ensure(info.IgnoreUpdate or info.ignoreUpdate, false) + info.__menu = t + info.NewIndex = function(t, k, v) + if (k == 'Value') then + local value = U:Ensure(v, false) + + if (value) then + t:Trigger('confirm', t) + else + t:Trigger('deny', t) + end + end + end + + ---@class ConfirmItem : Item + ---@filed private __event string Name of primary event + ---@field public UUID string UUID of Item + ---@field public Icon string Icon/Emoji for Item + ---@field public Label string Label of Item + ---@field public Description string Description of Item + ---@field public Value any Value of Item + ---@field public Values table[] List of values + ---@field public Min number Min range value + ---@field public Max number Max range value + ---@field public Disabled boolean Disabled state of Item + ---@field private Events table List of registered `on` events + ---@field public Trigger fun(t: Item, event: string) + ---@field public On fun(t: Item, event: string, func: function) + ---@field public Validate fun(t: Item, k: string, v:any) + ---@field public NewIndex fun(t: Item, k: string, v: any) + ---@field public GetValue fun(t: Item):any + ---@field public Confirm fun(t: Item) + ---@field public Deny fun(t: Item) + local item = CreateMenuItem(info) + + --- Confirm this item + function item:Confirm() item.Value = true end + --- Deny this item + function item:Deny() item.Value = false end + + if (info.TriggerUpdate) then + t.Items:AddItem(item) + else + local items = rawget(t.data, 'Items') + + if (items) then + local newIndex = #items + 1 + + rawset(items.data, newIndex, item) + + return items.data[newIndex] or item + end + end + + return t.Items[#t.Items] or item + end, + --- Create child menu from properties of this object + ---@param t Menu|string MenuV menu + ---@param overrides table Properties to override in menu object (ignore parent) + ---@param namespace string Namespace of menu + InheritMenu = function(t, overrides, namespace) + return MenuV:InheritMenu(t, overrides, namespace) + end, + --- Add control key for specific menu + ---@param t Menu|string MenuV menu + ---@param action string Name of action + ---@param func function This will be executed + ---@param description string Key description + ---@param defaultType string Default key type + ---@param defaultKey string Default key + AddControlKey = function(t, action, func, description, defaultType, defaultKey) + if (U:Typeof(t.Namespace) ~= 'string' or t.Namespace == 'unknown') then + error('[MenuV] Namespace is required for assigning keys.') + return + end + + MenuV:AddControlKey(t, action, func, description, defaultType, defaultKey) + end, + --- Assign key for opening this menu + ---@param t Menu|string MenuV menu + ---@param defaultType string Default key type + ---@param defaultKey string Default key + OpenWith = function(t, defaultType, defaultKey) + t:AddControlKey('open', function(m) + MenuV:CloseAll(function() + MenuV:OpenMenu(m) + end) + end, MenuV:T('open_menu'):format(t.Namespace), defaultType, defaultKey) + end, + --- Change title of menu + ---@param t Menu + ---@param title string Title of menu + SetTitle = function(t, title) + t.Title = U:Ensure(title, 'MenuV') + end, + --- Change subtitle of menu + ---@param t Menu + ---@param subtitle string Subtitle of menu + SetSubtitle = function(t, subtitle) + t.Subtitle = U:Ensure(subtitle, '') + end, + --- Change subtitle of menu + ---@param t Menu + ---@param position string | "'topleft'" | "'topcenter'" | "'topright'" | "'centerleft'" | "'center'" | "'centerright'" | "'bottomleft'" | "'bottomcenter'" | "'bottomright'" + SetPosition = function(t, position) + t.Position = U:Ensure(position, 'topleft') + end, + --- Clear all Menu items + ---@param t Menu + ClearItems = function(t, update) + update = U:Ensure(update, true) + + local items = CreateEmptyItemsTable({}) + + items(function(_, trigger, key, index, value, oldValue) + t:Trigger(trigger, key, index, value, oldValue) + end) + + rawset(t.data, 'Items', items) + + if (update and t.Trigger ~= nil and type(t.Trigger) == 'function') then + t:Trigger('update', 'Items', items) + end + end, + Open = function(t) + MenuV:OpenMenu(t) + end, + Close = function(t) + MenuV:CloseMenu(t) + end, + --- @see Menu to @see table + ---@param t Menu + ---@return table + ToTable = function(t) + local tempTable = { + theme = U:Ensure(t.Theme, 'default'), + uuid = U:Ensure(t.UUID, '00000000-0000-0000-0000-000000000000'), + title = U:Ensure(t.Title, 'MenuV'), + subtitle = U:Ensure(t.Subtitle, ''), + position = U:Ensure(t.Position, 'topleft'), + size = U:Ensure(t.Size, 'size-110'), + dictionary = U:Ensure(t.Dictionary, 'menuv'), + texture = U:Ensure(t.Texture, 'default'), + color = { + r = U:Ensure(t.Color.R, 0), + g = U:Ensure(t.Color.G, 0), + b = U:Ensure(t.Color.B, 255) + }, + items = {} + } + + local items = rawget(t.data, 'Items') + + if (items ~= nil and items.ToTable ~= nil) then + tempTable.items = items:ToTable() + end + + if (tempTable.color.r <= 0) then tempTable.color.r = 0 end + if (tempTable.color.r >= 255) then tempTable.color.r = 255 end + if (tempTable.color.g <= 0) then tempTable.color.g = 0 end + if (tempTable.color.g >= 255) then tempTable.color.g = 255 end + if (tempTable.color.b <= 0) then tempTable.color.b = 0 end + if (tempTable.color.b >= 255) then tempTable.color.b = 255 end + + return tempTable + end + } + + if (lower(item.Texture) == 'default' and lower(item.Dictionary) == 'menuv' and theme == 'native') then + item.Texture = 'default_native' + end + + item.Events.OnOpen = {} + item.Events.OnClose = {} + item.Events.OnSelect = {} + item.Events.OnUpdate = {} + item.Events.OnSwitch = {} + item.Events.OnChange = {} + item.Events.OnIChange = {} + + if (not U:Any(item.Size, { 'size-100', 'size-110', 'size-125', 'size-150', 'size-175', 'size-200' }, 'value')) then + item.Size = 'size-110' + end + + local mt = { + __index = function(t, k) + return rawget(t.data, k) + end, + ---@param t Menu + __tostring = function(t) + return encode(t:ToTable()) + end, + __call = function(t) + MenuV:OpenMenu(t) + end, + __newindex = function(t, k, v) + local whitelisted = { 'Title', 'Subtitle', 'Position', 'Color', 'R', 'G', 'B', 'Size', 'Dictionary', 'Texture', 'Theme' } + local key = U:Ensure(k, 'unknown') + local oldValue = rawget(t.data, k) + + if (not U:Any(key, whitelisted, 'value') and oldValue ~= nil) then + return + end + + local checkInput = t.Validate ~= nil and type(t.Validate) == 'function' + local inputParser = t.Parser ~= nil and type(t.Parser) == 'function' + local updateIndexTrigger = t.NewIndex ~= nil and type(t.NewIndex) == 'function' + + if (checkInput) then + local result = t:Validate(key, v) + result = U:Ensure(result, true) + + if (not result) then + return + end + end + + if (inputParser) then + local parsedValue = t:Parser(key, v) + + v = parsedValue or v + end + + rawset(t.data, k, v) + + if (key == 'Theme' or key == 'theme') then + local theme_value = string.lower(U:Ensure(v, 'default')) + + if (theme_value == 'native') then + rawset(t.data, 'color', { R = 255, G = 255, B = 255 }) + + local texture = U:Ensure(rawget(t.data, 'Texture'), 'default') + + if (texture == 'default') then + rawset(t.data, 'Texture', 'default_native') + end + elseif (theme_value == 'default') then + local texture = U:Ensure(rawget(t.data, 'Texture'), 'default') + + if (texture == 'default_native') then + rawset(t.data, 'Texture', 'default') + end + end + end + + if (updateIndexTrigger) then + t:NewIndex(key, v) + end + + if (t.Trigger ~= nil and type(t.Trigger) == 'function') then + t:Trigger('update', key, v, oldValue) + end + end, + __len = function(t) + return #t.Items + end, + __pairs = function(t) + return pairs(rawget(t.data, 'Items') or {}) + end, + __ipairs = function(t) + return ipairs(rawget(t.data, 'Items') or {}) + end, + __metatable = 'MenuV', + } + + ---@class Menu + ---@field public IsOpen boolean `true` if menu is open, otherwise `false` + ---@field public UUID string UUID of Menu + ---@field public Title string Title of Menu + ---@field public Subtitle string Subtitle of Menu + ---@field public Position string | "'topleft'" | "'topcenter'" | "'topright'" | "'centerleft'" | "'center'" | "'centerright'" | "'bottomleft'" | "'bottomcenter'" | "'bottomright'" + ---@field public Texture string Name of texture example: "default" + ---@field public Dictionary string Name of dictionary example: "menuv" + ---@field public Color table Color of Menu + ---@field private Events table List of registered `on` events + ---@field public Items Item[] List of items + ---@field public Trigger fun(t: Item, event: string) + ---@field public On fun(t: Menu, event: string, func: function|Menu): string + ---@field public RemoveOnEvent fun(t: Menu, event: string, uuid: string) + ---@field public Validate fun(t: Menu, k: string, v:any) + ---@field public NewIndex fun(t: Menu, k: string, v: any) + ---@field public Parser fun(t: Menu, k: string, v: any) + ---@field public AddButton fun(t: Menu, info: table):Item + ---@field public AddCheckbox fun(t: Menu, info: table):Item + ---@field public AddSlider fun(t: Menu, info: table):SliderItem + ---@field public AddRange fun(t: Menu, info: table):RangeItem + ---@field public AddConfirm fun(t: Menu, info: table):ConfirmItem + ---@field public AddControlKey fun(t: Menu, action: string, func: function, description: string, defaultType: string, defaultKey: string) + ---@field public OpenWith fun(t: Menu, defaultType: string, defaultKey: string) + ---@field public SetTitle fun(t: Menu, title: string) + ---@field public SetSubtitle fun(t: Menu, subtitle: string) + ---@field public SetPosition fun(t: Menu, position: string) + ---@field public ClearItems fun(t: Menu) + ---@field public Open fun(t: Menu) + ---@field public Close fun(t: Menu) + ---@field public ToTable fun(t: Menu):table + local menu = setmetatable({ data = item, __class = 'Menu', __type = 'Menu' }, mt) + + menu.Items(function(items, trigger, key, index, value, oldValue) + menu:Trigger(trigger, key, index, value, oldValue) + end) + + for k, v in pairs(info or {}) do + local key = U:Ensure(k, 'unknown') + + if (key == 'unknown') then return end + + menu:On(key, v) + end + + LoadTextureDictionary(menu.Dictionary) + + return menu +end + +_ENV.CreateMenu = CreateMenu +_G.CreateMenu = CreateMenu diff --git a/resources/[standalone]/menuv/menuv/components/translations.lua b/resources/[standalone]/menuv/menuv/components/translations.lua new file mode 100644 index 0000000..4330710 --- /dev/null +++ b/resources/[standalone]/menuv/menuv/components/translations.lua @@ -0,0 +1,35 @@ +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +local assert = assert +local decode = assert(json.decode) + +--- FiveM globals +local LoadResourceFile = assert(LoadResourceFile) + +--- MenuV globals +---@type Utilities +local Utilities = assert(Utilities) + +--- Empty translations table +local translations = {} + +--- Load all translations +local lang = Utilities:Ensure((Config or {}).Language, 'en') +local translations_path = ('languages/%s.json'):format(lang) +local translations_raw = LoadResourceFile('menuv', translations_path) + +if (translations_raw) then + local transFile = decode(translations_raw) + + if (transFile) then translations = Utilities:Ensure(transFile.translations, {}) end +end + +_ENV.translations = translations +_G.translations = translations \ No newline at end of file diff --git a/resources/[standalone]/menuv/menuv/components/utilities.lua b/resources/[standalone]/menuv/menuv/components/utilities.lua new file mode 100644 index 0000000..bc84746 --- /dev/null +++ b/resources/[standalone]/menuv/menuv/components/utilities.lua @@ -0,0 +1,427 @@ +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +local assert = assert +local type = assert(type) +local tonumber = assert(tonumber) +local tostring = assert(tostring) +local lower = assert(string.lower) +local upper = assert(string.upper) +local sub = assert(string.sub) +local encode = assert(json.encode) +local decode = assert(json.decode) +local floor = assert(math.floor) +local random = assert(math.random) +local randomseed = assert(math.randomseed) +local rawget = assert(rawget) +local setmetatable = assert(setmetatable) + +--- FiveM globals +local GET_GAME_TIMER = assert(GetGameTimer) +local GET_CURRENT_RESOURCE_NAME = assert(GetCurrentResourceName) + +--- Utilities for MenuV +---@class Utilities +local Utilities = setmetatable({ __class = 'Utilities' }, {}) + +--- Returns `true` if `input` starts with `start`, otherwise `false` +---@param input string Checks if this string starts with `start` +---@param start string Checks if `input` starts with this +---@return boolean `true` if `input` starts with `start`, otherwise `false` +function Utilities:StartsWith(input, start) + if (self:Typeof(input) ~= 'string') then return false end + if (self:Typeof(start) == 'number') then start = tostring(start) end + if (self:Typeof(start) ~= 'string') then return false end + + return sub(input, 1, #start) == start +end + +--- Returns `true` if `input` ends with `ends`, otherwise `false` +---@param input string Checks if this string ends with `ends` +---@param ends string Checks if `input` ends with this +---@return boolean `true` if `input` ends with `ends`, otherwise `false` +function Utilities:EndsWith(input, ends) + if (self:Typeof(input) ~= 'string') then return false end + if (self:Typeof(ends) == 'number') then ends = tostring(ends) end + if (self:Typeof(ends) ~= 'string') then return false end + + return sub(input, -#ends) == ends +end + +--- Returns the type of given `input` +---@param input any Any input +---@return string Type of given input +function Utilities:Typeof(input) + if (input == nil) then return 'nil' end + + local rawType = type(input) or 'nil' + + if (rawType ~= 'table') then return rawType end + + local isFXFunction = rawget(input, '__cfx_functionReference') ~= nil or + rawget(input, '__cfx_async_retval') ~= nil + + if (isFXFunction) then return 'function' end + if (rawget(input, '__cfx_functionSource') ~= nil) then return 'number' end + + local rawClass = rawget(input, '__class') + + if (rawClass ~= nil) then return type(rawClass) == 'string' and rawClass or 'class' end + + local rawTableType = rawget(input, '__type') + + if (rawTableType ~= nil) then return type(rawTableType) == 'string' and rawTableType or 'table' end + + return rawType +end + +local INPUT_GROUPS = { + [0] = "KEYBOARD", + [2] = "CONTROLLER" +} + +local INPUT_TYPE_GROUPS = { + ["KEYBOARD"] = 0, + ["MOUSE_ABSOLUTEAXIS"] = 0, + ["MOUSE_CENTEREDAXIS"] = 0, + ["MOUSE_RELATIVEAXIS"] = 0, + ["MOUSE_SCALEDAXIS"] = 0, + ["MOUSE_NORMALIZED"] = 0, + ["MOUSE_WHEEL"] = 0, + ["MOUSE_BUTTON"] = 0, + ["MOUSE_BUTTONANY"] = 0, + ["MKB_AXIS"] = 0, + ["PAD_AXIS"] = 2, + ["PAD_DIGITALBUTTON"] = 2, + ["PAD_DIGITALBUTTONANY"] = 2, + ["PAD_ANALOGBUTTON"] = 2, + ["JOYSTICK_POV"] = 2, + ["JOYSTICK_POV_AXIS"] = 2, + ["JOYSTICK_BUTTON"] = 2, + ["JOYSTICK_AXIS"] = 2, + ["JOYSTICK_IAXIS"] = 2, + ["JOYSTICK_AXIS_NEGATIVE"] = 2, + ["JOYSTICK_AXIS_POSITIVE"] = 2, + ["PAD_DEBUGBUTTON"] = 2, + ["GAME_CONTROLLED"] = 2, + ["DIGITALBUTTON_AXIS"] = 2, +} + +function Utilities:GetInputTypeGroup(inputType) + return INPUT_TYPE_GROUPS[inputType] or 0 +end + +function Utilities:GetInputGroupName(inputTypeGroup) + return INPUT_GROUPS[inputTypeGroup] or "KEYBOARD" +end + +--- Transform any `input` to the same type as `defaultValue` +---@type function +---@param input any Transform this `input` to `defaultValue`'s type +---@param defaultValue any Returns this if `input` can't transformed to this type +---@param ignoreDefault boolean Don't return default value if this is true +---@return any Returns `input` matches the `defaultValue` type or `defaultValue` +function Utilities:Ensure(input, defaultValue, ignoreDefault) + ignoreDefault = type(ignoreDefault) == 'boolean' and ignoreDefault or false + + if (defaultValue == nil) then return nil end + + local requiredType = self:Typeof(defaultValue) + + if (requiredType == 'nil') then return nil end + + local inputType = self:Typeof(input) + + if (inputType == requiredType) then return input end + if (inputType == 'nil') then return defaultValue end + + if (requiredType == 'number') then + if (inputType == 'boolean') then return input and 1 or 0 end + + return tonumber(input) or (not ignoreDefault and defaultValue or nil) + end + + if (requiredType == 'string') then + if (inputType == 'boolean') then return input and 'yes' or 'no' end + if (inputType == 'vector3') then return encode({ x = input.x, y = input.y, z = input.z }) or (not ignoreDefault and defaultValue or nil) end + if (inputType == 'vector2') then return encode({ x = input.x, y = input.y }) or (not ignoreDefault and defaultValue or nil) end + if (inputType == 'table') then return encode(input) or (not ignoreDefault and defaultValue or nil) end + + local result = tostring(input) + + if (result == 'nil') then + return not ignoreDefault and defaultValue or 'nil' + end + + return result + end + + if (requiredType == 'boolean') then + if (inputType == 'string') then + input = lower(input) + + if (input == 'true' or input == '1' or input == 'yes' or input == 'y') then return true end + if (input == 'false' or input == '0' or input == 'no' or input == 'n') then return false end + + return (not ignoreDefault and defaultValue or nil) + end + + if (inputType == 'number') then + if (input == 1) then return true end + if (input == 0) then return false end + + return (not ignoreDefault and defaultValue or nil) + end + + return (not ignoreDefault and defaultValue or nil) + end + + if (requiredType == 'table') then + if (inputType == 'string') then + if (self:StartsWith(input, '{') and self:EndsWith(input, '}')) then + return decode(input) or (not ignoreDefault and defaultValue or nil) + end + + if (self:StartsWith(input, '[') and self:EndsWith(input, ']')) then + return decode(input) or (not ignoreDefault and defaultValue or nil) + end + + return (not ignoreDefault and defaultValue or nil) + end + + if (inputType == 'vector3') then return { x = input.x or 0, y = input.y or 0, z = input.z or 0 } end + if (inputType == 'vector2') then return { x = input.x or 0, y = input.y or 0 } end + + return (not ignoreDefault and defaultValue or nil) + end + + if (requiredType == 'vector3') then + if (inputType == 'table') then + local _x = self:Ensure(input.x, defaultValue.x) + local _y = self:Ensure(input.y, defaultValue.y) + local _z = self:Ensure(input.z, defaultValue.z) + + return vector3(_x, _y, _z) + end + + if (inputType == 'vector2') then + local _x = self:Ensure(input.x, defaultValue.x) + local _y = self:Ensure(input.y, defaultValue.y) + + return vector3(_x, _y, 0) + end + + if (inputType == 'number') then + return vector3(input, input, input) + end + + return (not ignoreDefault and defaultValue or nil) + end + + if (requiredType == 'vector2') then + if (inputType == 'table') then + local _x = self:Ensure(input.x, defaultValue.x) + local _y = self:Ensure(input.y, defaultValue.y) + + return vector2(_x, _y) + end + + if (inputType == 'vector3') then + local _x = self:Ensure(input.x, defaultValue.x) + local _y = self:Ensure(input.y, defaultValue.y) + + return vector2(_x, _y) + end + + if (inputType == 'number') then + return vector2(input, input) + end + + return (not ignoreDefault and defaultValue or nil) + end + + return (not ignoreDefault and defaultValue or nil) +end + +--- Checks if input exists in inputs +--- '0' and 0 are both the same '0' == 0 equals `true` +--- 'yes' and true are both the same 'yes' == true equals `true` +---@param input any Any input +---@param inputs any[] Any table +---@param checkType string | "'value'" | "'key'" | "'both'" +---@return boolean Returns `true` if input has been found as `key` and/or `value` +function Utilities:Any(input, inputs, checkType) + if (input == nil) then return false end + if (inputs == nil) then return false end + + inputs = self:Ensure(inputs, {}) + checkType = lower(self:Ensure(checkType, 'value')) + + local checkMethod = 1 + + if (checkType == 'value' or checkType == 'v') then + checkMethod = 1 + elseif (checkType == 'key' or checkType == 'k') then + checkMethod = -1 + elseif (checkType == 'both' or checkType == 'b') then + checkMethod = 0 + end + + for k, v in pairs(inputs) do + if (checkMethod == 0 or checkMethod == -1) then + local checkK = self:Ensure(input, k, true) + + if (checkK ~= nil and checkK == k) then return true end + end + + if (checkMethod == 0 or checkMethod == 1) then + local checkV = self:Ensure(input, v, true) + + if (checkV ~= nil and checkV == v) then return true end + end + end + + return false +end + +--- Round any `value` +---@param value number Round this value +---@param decimal number Number of decimals +---@return number Rounded number +function Utilities:Round(value, decimal) + value = self:Ensure(value, 0) + decimal = self:Ensure(decimal, 0) + + if (decimal > 0) then + return floor((value * 10 ^ decimal) + 0.5) / (10 ^ decimal) + end + + return floor(value + 0.5) +end + +--- Checks if `item1` equals `item2` +---@param item1 any Item1 +---@param item2 any Item2 +---@return boolean `true` if both are equal, otherwise `false` +function Utilities:Equal(item1, item2) + if (item1 == nil and item2 == nil) then return true end + if (item1 == nil or item2 == nil) then return false end + + if (type(item1) == 'table') then + local item1EQ = rawget(item1, '__eq') + + if (item1EQ ~= nil and self:Typeof(item1EQ) == 'function') then + return item1EQ(item1, item2) + end + + return item1 == item2 + end + + if (type(item2) == 'table') then + local item2EQ = rawget(item2, '__eq') + + if (item2EQ ~= nil and self:Typeof(item2EQ) == 'function') then + return item2EQ(item2, item1) + end + + return item2 == item1 + end + + return item1 == item2 +end + +local function tohex(x) + x = Utilities:Ensure(x, 32) + + local s, base, d = '', 16 + + while x > 0 do + d = x % base + 1 + x = floor(x / base) + s = sub('0123456789abcdef', d, d) .. s + end + + while #s < 2 do s = ('0%s'):format(s) end + + return s +end + +local function bitwise(x, y, matrix) + x = Utilities:Ensure(x, 32) + y = Utilities:Ensure(y, 16) + matrix = Utilities:Ensure(matrix, {{0,0}, {0, 1}}) + + local z, pow = 0, 1 + + while x > 0 or y > 0 do + z = z + (matrix[x %2 + 1][y %2 + 1] * pow) + pow = pow * 2 + x = floor(x / 2) + y = floor(y / 2) + end + + return z +end + +--- Generates a random UUID like: 00000000-0000-0000-0000-000000000000 +---@return string Random generated UUID +function Utilities:UUID() + randomseed(GET_GAME_TIMER() + random(30720, 92160)) + + ---@type number[] + local bytes = { + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255), + random(0, 255) + } + + bytes[7] = bitwise(bytes[7], 0x0f, {{0,0},{0,1}}) + bytes[7] = bitwise(bytes[7], 0x40, {{0,1},{1,1}}) + bytes[9] = bitwise(bytes[7], 0x3f, {{0,0},{0,1}}) + bytes[9] = bitwise(bytes[7], 0x80, {{0,1},{1,1}}) + + return upper(('%s%s%s%s-%s%s-%s%s-%s%s-%s%s%s%s%s%s'):format( + tohex(bytes[1]), tohex(bytes[2]), tohex(bytes[3]), tohex(bytes[4]), + tohex(bytes[5]), tohex(bytes[6]), + tohex(bytes[7]), tohex(bytes[8]), + tohex(bytes[9]), tohex(bytes[10]), + tohex(bytes[11]), tohex(bytes[12]), tohex(bytes[13]), tohex(bytes[14]), tohex(bytes[15]), tohex(bytes[16]) + )) +end + +--- Replace a string that contains `this` to `that` +---@param str string String where to replace in +---@param this string Word that's need to be replaced +---@param that string Replace `this` whit given string +---@return string String where `this` has been replaced with `that` +function Utilities:Replace(str, this, that) + local b, e = str:find(this, 1, true) + + if b == nil then + return str + else + return str:sub(1, b - 1) .. that .. self:Replace(str:sub(e + 1), this, that) + end +end + +_G.Utilities = Utilities +_ENV.Utilities = Utilities \ No newline at end of file diff --git a/resources/[standalone]/menuv/menuv/menuv.lua b/resources/[standalone]/menuv/menuv/menuv.lua new file mode 100644 index 0000000..968dafb --- /dev/null +++ b/resources/[standalone]/menuv/menuv/menuv.lua @@ -0,0 +1,293 @@ +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu library for creating menu's +----------------------- [ MenuV ] ----------------------- +local assert = assert +local load = assert(load) +local xpcall = assert(xpcall) +local lower = assert(string.lower) +local upper = assert(string.upper) +local rawget = assert(rawget) +local rawset = assert(rawset) +local traceback = assert(debug.traceback) +local setmetatable = assert(setmetatable) + +--- FiveM globals +local GetInvokingResource = assert(GetInvokingResource) +local LoadResourceFile = assert(LoadResourceFile) +local RegisterKeyMapping = assert(RegisterKeyMapping) +local RegisterCommand = assert(RegisterCommand) +local SendNUIMessage = assert(SendNUIMessage) +local RegisterNUICallback = assert(RegisterNUICallback) +local IsScreenFadedOut = assert(IsScreenFadedOut) +local IsPauseMenuActive = assert(IsPauseMenuActive) +local PlaySoundFrontend = assert(PlaySoundFrontend) +local CreateThread = assert(Citizen.CreateThread) +local Wait = assert(Citizen.Wait) +local exports = assert(exports) + +--- MenuV globals +---@type Utilities +local Utilities = assert(Utilities) + +--- Load a file from `menuv` +---@param path string Path in `menuv` +---@return any|nil Results of nil +local function load_file(path) + if (path == nil or type(path) ~= 'string') then return nil end + + local raw_file = LoadResourceFile('menuv', path) + + if (raw_file) then + local raw_func, _ = load(raw_file, ('menuv/%s'):format(path), 't', _ENV) + + if (raw_func) then + local ok, result = xpcall(raw_func, traceback) + + if (ok) then + return result + end + end + end + + return nil +end + +load_file('menuv/components/translations.lua') + +local MenuV = setmetatable({ + ---@type string + __class = 'MenuV', + ---@type string + __type = 'MenuV', + ---@type boolean + Loaded = false, + ---@type number + ThreadWait = Utilities:Ensure((Config or {}).HideInterval, 250), + ---@type table + Translations = translations or {}, + ---@type table + Sounds = Utilities:Ensure((Config or {}).Sounds, {}), + ---@type boolean + Hidden = false +}, {}) + +MenuV.Keys = setmetatable({ data = {}, __class = 'MenuVKeys', __type = 'keys' }, { + __index = function(t, k) + return rawget(t.data, k) + end, + __newindex = function(t, k, v) + k = Utilities:Ensure(k, 'unknown') + + if (k == 'unknown') then return end + + local rawKey = rawget(t.data, k) + local keyExists = rawKey ~= nil + local prevState = Utilities:Ensure((rawKey or {}).status, false) + local newState = Utilities:Ensure(v, false) + + if (keyExists and not MenuV.Hidden) then + rawset(t.data[k], 'status', newState) + + if (prevState ~= newState) then + local action = newState and not prevState and 'KEY_PRESSED' or 'KEY_RELEASED' + local key = Utilities:Ensure(rawKey.action, 'UNKNOWN') + + SendNUIMessage({ action = action, key = key }) + end + end + end, + __call = function(t, k, a, inputType) + k = Utilities:Ensure(k, 'unknown') + a = Utilities:Ensure(a, 'UNKNOWN') + inputType = Utilities:Ensure(inputType, 0) + + if (k == 'unknown') then return end + + local rawKey = rawget(t.data, k) + local keyExists = rawKey ~= nil + + if (keyExists) then + if not rawKey.inputTypes[inputType] then + rawKey.inputTypes[inputType] = true + end + + return + end + + rawset(t.data, k, { status = false, action = a, inputTypes = { [inputType] = true } }) + end +}) + +--- Register a `action` with custom keybind +---@param action string Action like: UP, DOWN, LEFT... +---@param description string Description of keybind +---@param defaultType string Type like: keyboard, mouse etc. +---@param defaultKey string Default key for this keybind +function MenuV:RegisterKey(action, description, defaultType, defaultKey) + action = Utilities:Ensure(action, 'UNKNOWN') + description = Utilities:Ensure(description, 'unknown') + defaultType = Utilities:Ensure(defaultType, 'KEYBOARD') + defaultType = upper(defaultType) + defaultKey = Utilities:Ensure(defaultKey, 'F12') + + action = Utilities:Replace(action, ' ', '_') + action = upper(action) + + local typeGroup = Utilities:GetInputTypeGroup(defaultType) + + if (self.Keys[action] and self.Keys[action].inputTypes[typeGroup]) then return end + + self.Keys(action, action, typeGroup) + + local k = lower(action) + + if typeGroup > 0 then + local inputGroupName = Utilities:GetInputGroupName(typeGroup) + k = ('%s_%s'):format(lower(inputGroupName), k) + end + + k = ('menuv_%s'):format(k) + + RegisterKeyMapping(('+%s'):format(k), description, defaultType, defaultKey) + RegisterCommand(('+%s'):format(k), function() MenuV.Keys[action] = true end) + RegisterCommand(('-%s'):format(k), function() MenuV.Keys[action] = false end) +end + +--- Load translation +---@param k string Translation key +---@return string Translation or 'MISSING TRANSLATION' +local function T(k) + k = Utilities:Ensure(k, 'unknown') + + return Utilities:Ensure(MenuV.Translations[k], 'MISSING TRANSLATION') +end + +RegisterNUICallback('loaded', function(_, cb) + MenuV.Loaded = true + cb('ok') +end) + +RegisterNUICallback('sound', function(info, cb) + local key = upper(Utilities:Ensure(info.key, 'UNKNOWN')) + + if (MenuV.Sounds == nil and MenuV.Sounds[key] == nil) then cb('ok') return end + + local sound = Utilities:Ensure(MenuV.Sounds[key], {}) + local soundType = lower(Utilities:Ensure(sound.type, 'unknown')) + + if (soundType == 'native') then + local name = Utilities:Ensure(sound.name, 'UNKNOWN') + local library = Utilities:Ensure(sound.library, 'UNKNOWN') + + PlaySoundFrontend(-1, name, library, true) + end + + cb('ok') +end) + +--- Trigger the NUICallback for the right resource +---@param name string Name of callback +---@param info table Info returns from callback +---@param cb function Trigger this when callback is done +local function TriggerResourceCallback(name, info, cb) + local r = Utilities:Ensure(info.r, 'menuv') + + if (r == 'menuv') then cb('ok') return end + + local resource = exports[r] or nil + + if (resource == nil) then cb('ok') return end + + local nuiCallback = resource['NUICallback'] or nil + + if (nuiCallback == nil) then cb('ok') return end + + exports[r]:NUICallback(name, info, cb) +end + +RegisterNUICallback('submit', function(info, cb) TriggerResourceCallback('submit', info, cb) end) +RegisterNUICallback('close', function(info, cb) TriggerResourceCallback('close', info, cb) end) +RegisterNUICallback('switch', function(info, cb) TriggerResourceCallback('switch', info, cb) end) +RegisterNUICallback('update', function(info, cb) TriggerResourceCallback('update', info, cb) end) +RegisterNUICallback('open', function(info, cb) TriggerResourceCallback('open', info, cb) end) +RegisterNUICallback('opened', function(info, cb) TriggerResourceCallback('opened', info, cb) end) +RegisterNUICallback('close_all', function(info, cb) TriggerResourceCallback('close_all', info, cb) end) + +--- MenuV exports +exports('IsLoaded', function(cb) + cb = Utilities:Ensure(cb, function() end) + + if (MenuV.Loaded) then + cb() + return + end + + CreateThread(function() + local callback = cb + + repeat Wait(0) until MenuV.Loaded + + callback() + end) +end) + +exports('SendNUIMessage', function(input) + local r = Utilities:Ensure(GetInvokingResource(), 'menuv') + + if (Utilities:Typeof(input) == 'table') then + if (input.menu) then + rawset(input.menu, 'resource', r) + rawset(input.menu, 'defaultSounds', MenuV.Sounds) + rawset(input.menu, 'hidden', MenuV.Hidden) + end + + SendNUIMessage(input) + end +end) + +--- Register `MenuV` keybinds +MenuV:RegisterKey('UP', T('keybind_key_up'), 'KEYBOARD', 'UP') +MenuV:RegisterKey('DOWN', T('keybind_key_down'), 'KEYBOARD', 'DOWN') +MenuV:RegisterKey('LEFT', T('keybind_key_left'), 'KEYBOARD', 'LEFT') +MenuV:RegisterKey('RIGHT', T('keybind_key_right'), 'KEYBOARD', 'RIGHT') +MenuV:RegisterKey('ENTER', T('keybind_key_enter'), 'KEYBOARD', 'RETURN') +MenuV:RegisterKey('CLOSE', T('keybind_key_close'), 'KEYBOARD', 'BACK') +MenuV:RegisterKey('CLOSE_ALL', T('keybind_key_close_all'), 'KEYBOARD', 'PLUS') + +MenuV:RegisterKey('UP', ('%s - %s'):format(T('controller'), T('keybind_key_up')), 'PAD_ANALOGBUTTON', 'LUP_INDEX') +MenuV:RegisterKey('DOWN', ('%s - %s'):format(T('controller'), T('keybind_key_down')), 'PAD_ANALOGBUTTON', 'LDOWN_INDEX') +MenuV:RegisterKey('LEFT', ('%s - %s'):format(T('controller'), T('keybind_key_left')), 'PAD_ANALOGBUTTON', 'LLEFT_INDEX') +MenuV:RegisterKey('RIGHT', ('%s - %s'):format(T('controller'), T('keybind_key_right')), 'PAD_ANALOGBUTTON', 'LRIGHT_INDEX') +MenuV:RegisterKey('ENTER', ('%s - %s'):format(T('controller'), T('keybind_key_enter')), 'PAD_ANALOGBUTTON', 'RDOWN_INDEX') +MenuV:RegisterKey('CLOSE', ('%s - %s'):format(T('controller'), T('keybind_key_close')), 'PAD_ANALOGBUTTON', 'RRIGHT_INDEX') +MenuV:RegisterKey('CLOSE_ALL', ('%s - %s'):format(T('controller'), T('keybind_key_close_all')), 'PAD_ANALOGBUTTON', 'R3_INDEX') + +--- Hide menu when screen is faded out or pause menu ia active +CreateThread(function() + MenuV.Hidden = false + + while true do + repeat Wait(0) until MenuV.Loaded + + local new_state = IsScreenFadedOut() or IsPauseMenuActive() + + if (MenuV.Hidden ~= new_state) then + SendNUIMessage({ action = 'UPDATE_STATUS', status = not new_state }) + end + + MenuV.Hidden = new_state + + Wait(MenuV.ThreadWait) + end +end) + +--- When resource is stopped +AddEventHandler('onResourceStop', function(resourceName) + SendNUIMessage({ action = 'RESOURCE_STOPPED', resource = resourceName }) +end) \ No newline at end of file diff --git a/resources/[standalone]/menuv/menuv_example/example.lua b/resources/[standalone]/menuv/menuv_example/example.lua new file mode 100644 index 0000000..1d5cf6f --- /dev/null +++ b/resources/[standalone]/menuv/menuv_example/example.lua @@ -0,0 +1,45 @@ +--- MenuV Menu +---@type Menu +local menu = MenuV:CreateMenu(false, 'Welcome to MenuV', 'topleft', 255, 0, 0, 'size-125', 'example', 'menuv', 'example_namespace') +local menu2 = MenuV:CreateMenu('Demo 2', 'Open this demo menu in MenuV', 'topleft', 255, 0, 0) + +local menu_button = menu:AddButton({ icon = '😃', label = 'Open Demo 2 Menu', value = menu2, description = 'YEA :D from first menu' }) +local menu2_button = menu2:AddButton({ icon = '😃', label = 'Open First Menu', value = menu, description = 'YEA :D from second menu' }) +local confirm = menu:AddConfirm({ icon = '🔥', label = 'Confirm', value = 'no' }) +local range = menu:AddRange({ icon = '⚽', label = 'Range Item', min = 0, max = 10, value = 0, saveOnUpdate = true }) +local checkbox = menu:AddCheckbox({ icon = '💡', label = 'Checkbox Item', value = 'n' }) +local checkbox_disabled = menu:AddCheckbox({ icon = '💡', label = 'Checkbox Disabled', value = 'n', disabled = true }) +local slider = menu:AddSlider({ icon = '❤️', label = 'Slider', value = 'demo', values = { + { label = 'Demo Item', value = 'demo', description = 'Demo Item 1' }, + { label = 'Demo Item 2', value = 'demo2', description = 'Demo Item 2' }, + { label = 'Demo Item 3', value = 'demo3', description = 'Demo Item 3' }, + { label = 'Demo Item 4', value = 'demo4', description = 'Demo Item 4' } +}}) + +--- Events +confirm:On('confirm', function(item) print('YOU ACCEPTED THE TERMS') end) +confirm:On('deny', function(item) print('YOU DENIED THE TERMS') end) + +range:On('select', function(item, value) print(('FROM %s to %s YOU SELECTED %s'):format(item.Min, item.Max, value)) end) +range:On('change', function(item, newValue, oldValue) + menu.Title = ('MenuV %s'):format(newValue) +end) + +slider:On('select', function(item, value) print(('YOU SELECTED %s'):format(value)) end) + +confirm:On('enter', function(item) print('YOU HAVE NOW A CONFIRM ACTIVE') end) +confirm:On('leave', function(item) print('YOU LEFT OUR CONFIRM :(') end) + +menu:On('switch', function(item, currentItem, prevItem) print(('YOU HAVE SWITCH THE ITEMS FROM %s TO %s'):format(prevItem.__type, currentItem.__type)) end) + +menu2:On('open', function(m) + m:ClearItems() + + for i = 1, 10, 1 do + math.randomseed(GetGameTimer() + i) + + m:AddButton({ ignoreUpdate = i ~= 10, icon = '❤️', label = ('Open Menu %s'):format(math.random(0, 1000)), value = menu, description = ('YEA! ANOTHER RANDOM NUMBER: %s'):format(math.random(0, 1000)), select = function(i) print('YOU CLICKED ON THIS ITEM!!!!') end }) + end +end) + +menu:OpenWith('KEYBOARD', 'F1') -- Press F1 to open Menu \ No newline at end of file diff --git a/resources/[standalone]/menuv/menuv_example/fxmanifest.lua b/resources/[standalone]/menuv/menuv_example/fxmanifest.lua new file mode 100644 index 0000000..0946e13 --- /dev/null +++ b/resources/[standalone]/menuv/menuv_example/fxmanifest.lua @@ -0,0 +1,28 @@ +----------------------- [ MenuV ] ----------------------- +-- GitHub: https://github.com/ThymonA/menuv/ +-- License: GNU General Public License v3.0 +-- https://choosealicense.com/licenses/gpl-3.0/ +-- Author: Thymon Arens +-- Name: MenuV +-- Version: 1.4.1 +-- Description: FiveM menu libarary for creating menu's +----------------------- [ MenuV ] ----------------------- + +fx_version 'cerulean' +game 'gta5' + +name 'MenuV' +version '1.4.1' +description 'FiveM menu libarary for creating menu\'s' +author 'ThymonA' +contact 'contact@arens.io' +url 'https://github.com/ThymonA/menuv/' + +client_scripts { + '@menuv/menuv.lua', + 'example.lua' +} + +dependencies { + 'menuv' +} \ No newline at end of file diff --git a/resources/[standalone]/menuv/stream/menuv.ytd b/resources/[standalone]/menuv/stream/menuv.ytd new file mode 100644 index 0000000..06d2039 Binary files /dev/null and b/resources/[standalone]/menuv/stream/menuv.ytd differ diff --git a/resources/[standalone]/menuv/templates/default.png b/resources/[standalone]/menuv/templates/default.png new file mode 100644 index 0000000..ff207ab Binary files /dev/null and b/resources/[standalone]/menuv/templates/default.png differ diff --git a/resources/[standalone]/menuv/templates/default_native.png b/resources/[standalone]/menuv/templates/default_native.png new file mode 100644 index 0000000..9b23d8e Binary files /dev/null and b/resources/[standalone]/menuv/templates/default_native.png differ diff --git a/resources/[standalone]/menuv/templates/example.png b/resources/[standalone]/menuv/templates/example.png new file mode 100644 index 0000000..6946743 Binary files /dev/null and b/resources/[standalone]/menuv/templates/example.png differ diff --git a/resources/[standalone]/menuv/templates/menuv.ytd b/resources/[standalone]/menuv/templates/menuv.ytd new file mode 100644 index 0000000..06d2039 Binary files /dev/null and b/resources/[standalone]/menuv/templates/menuv.ytd differ diff --git a/resources/[standalone]/menuv/templates/template.psd b/resources/[standalone]/menuv/templates/template.psd new file mode 100644 index 0000000..9fe1e62 Binary files /dev/null and b/resources/[standalone]/menuv/templates/template.psd differ