diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e72bfdd --- /dev/null +++ b/COPYING @@ -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/COPYING.LESSER b/COPYING.LESSER new file mode 100644 index 0000000..153d416 --- /dev/null +++ b/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER 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. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/DmResEditor/DmResEditor.cpp b/DmResEditor/DmResEditor.cpp new file mode 100644 index 0000000..91c1a1b --- /dev/null +++ b/DmResEditor/DmResEditor.cpp @@ -0,0 +1,889 @@ +// DmResEditor.cpp : 定义应用程序的入口点。 +// +// 8-12-2020 Create + +#include "framework.h" +#include "DmResEditor.h" +#include + +HINSTANCE m_hInstance; +HWND g_hWnd; +std::wstring m_title = L"梦月资源编辑器v1.1"; + +bool g_istip = false; + +using namespace Ctrl; + +#if MUI_CFG_ENABLE_V1DMRES + +DMResources dmRes; + +XML::MuiXML* m_xmlui = nullptr; + +extern void blockcallback(_m_size size, _m_size allsize, _m_param param); + +static HRESULT WINAPI GetDpiForMonitor( + _In_ HMONITOR hmonitor, + _In_ MONITOR_DPI_TYPE dpiType, + _Out_ UINT* dpiX, + _Out_ UINT* dpiY) +{ + HINSTANCE hInstWinSta = LoadLibraryW(L"SHCore.dll"); + if (hInstWinSta == nullptr) return E_NOINTERFACE; + + typedef HRESULT(WINAPI* PFN_GDFM)( + HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + + PFN_GDFM pGetDpiForMonitor = + (PFN_GDFM)GetProcAddress(hInstWinSta, "GetDpiForMonitor"); + if (pGetDpiForMonitor == nullptr) return E_NOINTERFACE; + + return pGetDpiForMonitor(hmonitor, dpiType, dpiX, dpiY); +} + +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ + CtrlMgr::RegisterMuiControl(); + + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + //ShowDebugRect = true; + m_hInstance = hInstance; + std::wstring error; + + Render::MRender* render = new Render::MRender_GDIP(); + + UINT_PTR gdiplusToken = 0; + Gdiplus::GdiplusStartupInput gdiplusStartupInput; + Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); + + auto window = new MainWindow(render); + if (window->Create(0, m_title, UIRect(CW_USEDEFAULT, CW_USEDEFAULT, 624, 652), + std::bind(&MainWindow::AfterCreated, window), (_m_param)(WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX), 0)) + { + window->SetMainWindow(true); + + //DPI缩放 + UINT dpiX = 0; + UINT dpiY = 0; + HRESULT hr = GetDpiForMonitor( + MonitorFromWindow((HWND)window->GetWindowHandle(), MONITOR_DEFAULTTONEAREST), + MDT_EFFECTIVE_DPI, (UINT*)&dpiX, (UINT*)&dpiY); + if (FAILED(hr)) { + HDC hdc = GetDC(NULL); + dpiX = GetDeviceCaps(hdc, LOGPIXELSX); + ReleaseDC(NULL, hdc); + } + float scale = (float)dpiX / 96.f; + window->ScaleWindow(int(624 * scale), int(652 * scale)); + + window->SetCacheMode(true); + window->CenterWindow(); + //window.ShowDebugRect(true); + window->ShowWindow(true); + + Settings::UIMessageLoop(); + } + + Gdiplus::GdiplusShutdown(gdiplusToken); + return 0; +} + +_m_result MainWindow::EventSource(MEventCodeEnum code, _m_param param) +{ + //响应DPI更改 + auto message = ConvertEventCode(code); + if (message == WM_DPICHANGED) + { + thread_local bool flag = false; + if (flag) + return 0; + flag = true; + auto pm = (std::pair<_m_param, _m_param>*)param; + UINT dpiX = Helper::M_LOWORD((_m_long)pm->first); + float scale = (float)dpiX / 96.f; + ScaleWindow(int(624.f * scale), int(652.f * scale)); + flag = false; + return 0; + } + else if(message == WM_DESTROY) + { + delete this; + return 0; + } + return UIWindowBasic::EventSource(code, param); +} + +bool MainWindow::AfterCreated() +{ + g_hWnd = (HWND)GetWindowHandle(); + HICON hIcon = LoadIconW(m_hInstance, MAKEINTRESOURCEW(IDI_DMRESEDITOR)); + SendMessageW(g_hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + CreateControls(); + return true; +} + +bool MainWindow::EventProc(UINotifyEvent event, UIControl* control, _m_param param) +{ + if (MUIEVENT(Event_Mouse_LClick, L"browse")) + { + std::vector file; + if (FS::UI::MBrowseForFile(false, false, + { + { L"梦月资源文件 (*.dmres;*.dmspt)", L"*.dmres;*.dmspt" }, + { L"所有文件 All Files (*.*)", L"*.*" } + }, (_m_param)g_hWnd, file)) + { + static_cast(GetRootControl()->Child(L"path"))->SetCurText(file[0]); + } + } + //新建资源 + else if (MUIEVENT(Event_Mouse_LClick, L"newdmres")) + { + std::wstring path = static_cast(GetRootControl()->Child(L"path"))->GetCurText(); + if (path != L"") + { + static_cast(GetRootControl()->Child(L"resclsname"))->SetCurText(L"DMResourceClass"); + control->SetEnabled(false); + + std::wstring resname = L"Resource1"; + std::wstring reskey = L"12345678"; + + dmRes.CreateResource(L"DMResourceClass"); + dmRes.AddResource({ UIResource(), false, 0, L"" }, resname, reskey); + + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + ListItem* item = new ListItem(); + item->SetText(resname); + listbox->AddItem(item, -1); + listbox->SetEnabled(true, false); + listbox->SetCurSelItem(0); + + static_cast(GetRootControl()->Child(L"resname"))->SetCurText(resname); + static_cast(GetRootControl()->Child(L"reskey"))->SetCurText(reskey); + + GetRootControl()->Child(L"importres")->SetEnabled(true, false); + GetRootControl()->Child(L"importstr")->SetEnabled(true, false); + GetRootControl()->Child(L"addres")->SetEnabled(true); + GetRootControl()->Child(L"delres")->SetEnabled(true); + GetRootControl()->Child(L"savefile")->SetEnabled(true); + GetRootControl()->Child(L"exitedit")->SetEnabled(true); + GetRootControl()->Child(L"loaddmres")->SetEnabled(false); + auto block = (UICheckBox*)GetRootControl()->Child(L"isblock"); + block->SetSel(false); + block->SetEnabled(true); + auto blocksize = (UIEditBox*)GetRootControl()->Child(L"blocksize"); + blocksize->SetCurText(L"1024"); + blocksize->SetEnabled(true); + GetRootControl()->Child(L"upresname")->SetEnabled(false); + + GetRootControl()->Child(L"group")->SetEnabled(true); + } + else + MessageBoxW(g_hWnd, L"文件路径不能为空!", L"失败", MB_ICONERROR); + } + //类名更改 + else if (MUIEVENT(Event_Edit_TextChanged, L"resclsname")) + { + GetRootControl()->Child(L"upclsname")->SetEnabled(true); + } + //更新类名 + else if (MUIEVENT(Event_Mouse_LClick, L"upclsname")) + { + UIEditBox* edit = (UIEditBox*)GetRootControl()->Child(L"resclsname"); + if (edit->GetCurText() == L"") + { + MessageBoxW(g_hWnd, L"更新名称失败!名称不能为空", L"更改类名", MB_ICONERROR); + return false; + } + control->SetEnabled(false); + dmRes.RenameClassName(edit->GetCurText().c_str()); + MessageBoxW(g_hWnd, (L"类名已更改为:" + edit->GetCurText()).c_str(), L"更改类名", MB_ICONINFORMATION); + } + //资源名更改 + else if (MUIEVENT(Event_Edit_TextChanged, L"resname")) + { + GetRootControl()->Child(L"upresname")->SetEnabled(true); + } + //更新资源名 + else if (MUIEVENT(Event_Mouse_LClick, L"upresname")) + { + UIEditBox* edit = (UIEditBox*)GetRootControl()->Child(L"resname"); + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + if (edit->GetCurText() == L"") + { + MessageBoxW(g_hWnd, L"更新名称失败!名称不能为空", L"更改资源名", MB_ICONERROR); + return true; + } + + if (dmRes.RenameResource(listbox->GetItem(listbox->GetCurSelItem())->GetText().data(), edit->GetCurText().c_str())) { + listbox->GetItem(listbox->GetCurSelItem())->SetText(edit->GetCurText()); + control->SetEnabled(false); + MessageBoxW(g_hWnd, (L"资源名已更改为:" + edit->GetCurText()).c_str(), L"更改资源名", MB_ICONINFORMATION); + } + else + MessageBoxW(g_hWnd, L"类名更新失败..", L"更改资源名", MB_ICONERROR); + } + //分块选项 + else if (MUIEVENT(Event_Mouse_LClick, L"isblock")) + { + GetRootControl()->Child(L"blocksize")->SetEnabled(static_cast(control)->GetSel()); + } + else if (MUIEVENT(Event_Focus_False, L"blocksize")) + { + UIEditBox* edit = (UIEditBox*)control; + if (edit->GetCurText() == L"") + edit->SetCurText(L"1024"); + } + //资源文本更改 + else if (MUIEVENT(Event_Edit_TextChanged, L"strpreview")) + { + UIEditBox* edit = (UIEditBox*)control; + if (edit->GetCurTextLength(0) == 0) + { + GetRootControl()->Child(L"exportstr")->SetEnabled(false); + GetRootControl()->Child(L"cleanstr")->SetEnabled(false); + } + else + { + GetRootControl()->Child(L"exportstr")->SetEnabled(true); + GetRootControl()->Child(L"importstr")->SetEnabled(true); + GetRootControl()->Child(L"cleanstr")->SetEnabled(true); + GetRootControl()->Child(L"savestr")->SetEnabled(true); + } + ShowStrLength(edit->GetCurTextLength(0)); + } + //写入数据 + else if (MUIEVENT(Event_Mouse_LClick, L"importres")) + { + WriteRes(control); + } + //清空数据 + else if (MUIEVENT(Event_Mouse_LClick, L"cleanres")) + { + if (MessageBoxW(g_hWnd, L"确定要清空资源数据吗?", L"清空数据", MB_ICONQUESTION | MB_YESNO) == IDYES) + { + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + UIEditBox* reskey = static_cast(GetRootControl()->Child(L"reskey")); + std::wstring resname = listbox->GetItem(listbox->GetCurSelItem())->GetText().data(); + + DMResKey key = dmRes.ReadResource(resname, reskey->GetCurText()); + if (key.res.data) + key.res.Release(); + if (dmRes.ChangeResource(resname, key, reskey->GetCurText())) + { + GetRootControl()->Child(L"exportres")->SetEnabled(false); + GetRootControl()->Child(L"importres")->SetEnabled(true); + GetRootControl()->Child(L"isblock")->SetEnabled(true); + GetRootControl()->Child(L"blocksize")->SetEnabled(true); + control->SetEnabled(false); + + ShowResSize(0); + + MessageBoxW(g_hWnd, L"清空资源数据成功!", L"清空数据", MB_ICONINFORMATION); + } + } + } + //写出数据 + else if (MUIEVENT(Event_Mouse_LClick, L"exportres")) + { + SaveRes(control); + } + //写出文本 + else if (MUIEVENT(Event_Mouse_LClick, L"exportstr")) + { + std::wstring str = static_cast(GetRootControl()->Child(L"strpreview"))->GetCurText(); + str = Helper::M_ReplaceString(str, L"\r", L"\r\n"); + std::vector selfile; + if(FS::UI::MBrowseForFile(false, false, {{L"文本文档 (*.txt)", L"*.txt"}}, (_m_param)g_hWnd, selfile)) + { + FILE* file = 0; + _wfopen_s(&file, selfile[0].data(), L"wb"); + if (!file) return true; + + //Unicode文件头 + fwrite("\xFF\xFE", 1, 2, file); + fwrite(str.c_str(), 1, str.length() * sizeof(wchar_t), file); + fclose(file); + } + } + //写入文本 + else if (MUIEVENT(Event_Mouse_LClick, L"importstr")) + { + std::vector selfile; + if (FS::UI::MBrowseForFile(true, false, { {L"UTF16 LE 文本文档 (*.txt)", L"*.txt"} }, (_m_param)g_hWnd, selfile)) + { + FILE* file = 0; + _wfopen_s(&file, selfile[0].data(), L"rb"); + if (!file) return true; + + bool end = false; + + fseek(file, 0L, SEEK_END); + _m_long len = ftell(file); + _m_long read = 0; + //跳过Unicode文件头 + fseek(file, 2, SEEK_SET); + std::wstring str; + while (!end) + { + wchar_t wch; + _m_long len_read = fread(&wch, 1, sizeof(wchar_t), file); + if (len_read) + { + read += len_read; + str += wch; + } + if (read + 2 >= len) + break; + } + static_cast(GetRootControl()->Child(L"strpreview"))->SetCurText(str); + ShowStrLength(str.length()); + fclose(file); + if (str.length() != 0) { + GetRootControl()->Child(L"exportstr")->SetEnabled(true); + GetRootControl()->Child(L"cleanstr")->SetEnabled(true); + } + GetRootControl()->Child(L"savestr")->SetEnabled(true); + } + } + //保存文本数据 + else if (MUIEVENT(Event_Mouse_LClick, L"savestr")) + { + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + UIEditBox* reskey = static_cast(GetRootControl()->Child(L"reskey")); + UIEditBox* resstrEdit = static_cast(GetRootControl()->Child(L"strpreview")); + std::wstring resname = listbox->GetItem(listbox->GetCurSelItem())->GetText().data(); + + DMResKey key = dmRes.ReadResource(resname.c_str(), reskey->GetCurText().c_str(), StringRes); + key.resText = resstrEdit->GetCurText(); + + if (dmRes.ChangeResource(resname.c_str(), key, reskey->GetCurText().c_str(), StringRes)) + { + control->SetEnabled(false); + MessageBoxW(g_hWnd, L"更新字符串资源成功!", L"保存文本", MB_ICONINFORMATION); + } + else + MessageBoxW(g_hWnd, L"更新字符串资源失败...", L"保存文本", MB_ICONERROR); + } + //清空文本数据 + else if (MUIEVENT(Event_Mouse_LClick, L"cleanstr")) + { + control->SetEnabled(false); + GetRootControl()->Child(L"exportstr")->SetEnabled(false); + static_cast(GetRootControl()->Child(L"strpreview"))->SetCurText(L""); + ShowStrLength(0); + GetRootControl()->Child(L"savestr")->SetEnabled(true); + } + //添加资源 + else if (MUIEVENT(Event_Mouse_LClick, L"addres")) + { + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + + std::wstring newResname = L"Resource" + std::to_wstring(listbox->GetItemListCount() + 1); + + ListItem* item = new ListItem(); + item->SetText(newResname); + listbox->AddItem(item, -1); + listbox->SetCurSelItem(listbox->GetItemListCount() - 1); + curSelItem = listbox->GetItemListCount() - 1; + + static_cast(GetRootControl()->Child(L"resname"))->SetCurText(newResname); + + GetRootControl()->Child(L"upresname")->SetEnabled(false); + + GetRootControl()->Child(L"importres")->SetEnabled(true); + GetRootControl()->Child(L"importstr")->SetEnabled(true); + GetRootControl()->Child(L"exportres")->SetEnabled(false); + GetRootControl()->Child(L"exportstr")->SetEnabled(false); + + auto block = (UICheckBox*)GetRootControl()->Child(L"isblock"); + block->SetSel(false, false); + block->SetEnabled(true); + + auto blocksize = (UIEditBox*)GetRootControl()->Child(L"blocksize"); + blocksize->SetCurText(L"1024"); + blocksize->SetEnabled(true); + + GetRootControl()->Child(L"decoderes")->SetEnabled(false); + GetRootControl()->Child(L"cleanres")->SetEnabled(false); + GetRootControl()->Child(L"cleanstr")->SetEnabled(false); + GetRootControl()->Child(L"savestr")->SetEnabled(false); + GetRootControl()->Child(L"strpreview")->SetEnabled(true); + + GetRootControl()->Child(L"delres")->SetEnabled(true); + GetRootControl()->Child(L"savefile")->SetEnabled(true); + + static_cast(GetRootControl()->Child(L"strpreview"))->SetCurText(L""); + ShowResSize(0); + ShowStrLength(0); + GetRootControl()->Child(L"group")->SetEnabled(true); + + UIEditBox* reskey = static_cast(GetRootControl()->Child(L"reskey")); + dmRes.AddResource({ UIResource(), false, 0, L"" }, newResname, reskey->GetCurText()); + } + //资源列表项目选中更改 + else if (MUIEVENT(Event_ListBox_ItemChanged, L"reslist")) + { + curSelItem = (int)param; + OnSelChange(control); + } + //解码资源 + else if (MUIEVENT(Event_Mouse_LClick, L"decoderes")) + { + UIEditBox* preview = static_cast(GetRootControl()->Child(L"strpreview")); + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + std::wstring resName = listbox->GetItem(listbox->GetCurSelItem())->GetText().data(); + UIEditBox* reskey = static_cast(GetRootControl()->Child(L"reskey")); + + DMResKey key = dmRes.ReadResource(resName, reskey->GetCurText(), StringRes); + + preview->SetCurText(key.resText); + preview->SetEnabled(true); + GetRootControl()->Child(L"importstr")->SetEnabled(true); + if (key.resText.length() != 0) { + GetRootControl()->Child(L"cleanstr")->SetEnabled(true); + GetRootControl()->Child(L"exportstr")->SetEnabled(true); + } + if (dmRes.ReadResourceSize(resName, DataRes) != 0) + { + GetRootControl()->Child(L"importres")->SetEnabled(false); + GetRootControl()->Child(L"cleanres")->SetEnabled(true); + GetRootControl()->Child(L"exportres")->SetEnabled(true); + } + else { + GetRootControl()->Child(L"importres")->SetEnabled(true); + GetRootControl()->Child(L"isblock")->SetEnabled(true); + GetRootControl()->Child(L"blocksize")->SetEnabled(true); + } + control->SetEnabled(false); + + if (!g_istip) { + g_istip = true; + MessageBoxW(g_hWnd, L"资源读取完毕\n提示:\n 如果秘钥与写入资源时的秘钥不符 依然能读取数据 但数据并非正确数据! 请自行效验结果.", L"读取数据", MB_ICONINFORMATION); + } + } + //删除资源 + else if (MUIEVENT(Event_Mouse_LClick, L"delres")) + { + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + std::wstring resName = listbox->GetItem(listbox->GetCurSelItem())->GetText().data(); + if (MessageBoxW(g_hWnd, (L"是否要删除资源\"" + resName + L"\"?").c_str(), L"删除资源", MB_ICONQUESTION | MB_YESNO) == IDYES) + { + if (dmRes.DeleteResource(resName)) { + listbox->DeleteItem(listbox->GetCurSelItem()); + listbox->SetCurSelItem(-1); + curSelItem = -1; + UIEditBox* preview = static_cast(GetRootControl()->Child(L"strpreview")); + preview->SetCurText(L""); + preview->SetEnabled(false); + UIEditBox* resName = static_cast(GetRootControl()->Child(L"resname")); + resName->SetCurText(L""); + + control->SetEnabled(false); + ShowStrLength(0); + ShowResSize(0); + static_cast(GetRootControl()->Child(L"isblock"))->SetSel(0); + static_cast(GetRootControl()->Child(L"blocksize"))->SetCurText(L"1024"); + GetRootControl()->Child(L"group")->SetEnabled(false); + + MessageBoxW(g_hWnd, L"删除资源成功!", L"删除资源", MB_ICONINFORMATION); + } + else + MessageBoxW(g_hWnd, L"删除资源失败..", L"删除资源", MB_ICONERROR); + } + } + else if (MUIEVENT(Event_Mouse_LClick, L"savefile")) + { + std::wstring path = static_cast(GetRootControl()->Child(L"path"))->GetCurText(); + if (dmRes.SaveResource(path)) + MessageBoxW(g_hWnd, L"资源以成功保存到指定路径~", L"保存", MB_ICONINFORMATION); + else + MessageBoxW(g_hWnd, L"保存文件失败! 文件可能被占用或不存在", L"保存", MB_ICONERROR); + } + else if (MUIEVENT(Event_Mouse_LClick, L"exitedit")) + { + if (MessageBoxW(g_hWnd, L"确定退出编辑模式,返回启动时的状态吗?\n提示:如果修改了资源而未保存文件 将会丢失!", L"退出编辑", MB_ICONQUESTION | MB_YESNO) == IDYES) + { + control->SetEnabled(false); + GetRootControl()->Child(L"loaddmres")->SetEnabled(true); + GetRootControl()->Child(L"newdmres")->SetEnabled(true); + GetRootControl()->Child(L"delres")->SetEnabled(false); + GetRootControl()->Child(L"addres")->SetEnabled(false); + GetRootControl()->Child(L"group")->SetEnabled(false); + GetRootControl()->Child(L"savefile")->SetEnabled(false); + GetRootControl()->Child(L"upclsname")->SetEnabled(false); + GetRootControl()->Child(L"exportres")->SetEnabled(false); + GetRootControl()->Child(L"exportstr")->SetEnabled(false); + GetRootControl()->Child(L"cleanres")->SetEnabled(false); + GetRootControl()->Child(L"cleanstr")->SetEnabled(false); + GetRootControl()->Child(L"savestr")->SetEnabled(false); + GetRootControl()->Child(L"decoderes")->SetEnabled(false); + static_cast(GetRootControl()->Child(L"resname"))->SetCurText(L""); + UIEditBox* clsName = static_cast(GetRootControl()->Child(L"resclsname")); + clsName->SetCurText(L""); + UIEditBox* resName = static_cast(GetRootControl()->Child(L"strpreview")); + resName->SetCurText(L""); + resName->SetEnabled(true); + static_cast(GetRootControl()->Child(L"reskey"))->SetCurText(L""); + ShowStrLength(0); + ShowResSize(0); + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + listbox->DeleteAllItem(); + listbox->SetEnabled(false); + dmRes.CloseResource(); + } + } + else if (MUIEVENT(Event_Mouse_LClick, L"loaddmres")) + { + std::wstring path = static_cast(GetRootControl()->Child(L"path"))->GetCurText(); + if (dmRes.LoadResource(path, true)) + { + control->SetEnabled(false); + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + + std::vector resList; + dmRes.EnumResourceName(resList); + for (auto name : resList) + { + ListItem* item = new ListItem(); + item->SetText(name); + listbox->AddItem(item, -1, false); + } + listbox->SetCurSelItem(-1); + listbox->SetEnabled(true); + + static_cast(GetRootControl()->Child(L"resclsname"))->SetCurText(dmRes.GetResClassName()); + + GetRootControl()->Child(L"savefile")->SetEnabled(true); + GetRootControl()->Child(L"loaddmres")->SetEnabled(false); + GetRootControl()->Child(L"exitedit")->SetEnabled(true); + GetRootControl()->Child(L"addres")->SetEnabled(true); + GetRootControl()->Child(L"newdmres")->SetEnabled(false); + + curSelItem = 0; + if (resList.size()) + { + listbox->SetCurSelItem(0); + OnSelChange(listbox); + } + } + else + MessageBoxW(g_hWnd, L"打开文件失败! ", L"保存", MB_ICONERROR); + } + return false; +} + +void MainWindow::CreateControls() +{ + //这些都是1.x 版本的写法 2x还能用! 懒得改了 + + UIControl* root_control = GetRootControl(); + + m_xmlui = XMLUI(); + auto mgr = m_xmlui->Mgr(); + + auto editstyle = DMResources::ReadPEResource(IDB_PNG1, L"PNG"); + auto btnstyle = DMResources::ReadPEResource(IDB_PNG2, L"PNG"); + auto liststyle = DMResources::ReadPEResource(IDB_PNG3, L"PNG"); + auto listitemstyle = DMResources::ReadPEResource(IDB_PNG4, L"PNG"); + auto scrollstyle = DMResources::ReadPEResource(IDB_PNG5, L"PNG"); + auto checkstyle = DMResources::ReadPEResource(IDB_PNG6, L"PNG"); + auto progstyle = DMResources::ReadPEResource(IDB_PNG7, L"PNG"); + + mgr->AddImageStyle(L"editbox", editstyle, 4, nullptr, true, { 2,2,2,2 }); + mgr->AddImageStyle(L"btnskin", btnstyle, 4, nullptr, true, { 2,2,2,2 }); + mgr->AddImageStyle(L"listbox", liststyle, 4, nullptr, true, { 2,2,2,2 }); + mgr->AddImageStyle(L"listitem", listitemstyle, 8, nullptr, true, { 5,5,5,5 }); + mgr->AddImageStyle(L"scroll", scrollstyle, 16, nullptr, true, { 2,2,2,2 }); + mgr->AddImageStyle(L"checkbox", checkstyle, 8, nullptr, true, { 5,5,5,5 }); + mgr->AddImageStyle(L"progskin", progstyle, 4, nullptr, true, { 1,1,1,1 }); + + editstyle.Release(); + btnstyle.Release(); + liststyle.Release(); + listitemstyle.Release(); + scrollstyle.Release(); + checkstyle.Release(); + progstyle.Release(); + + UILabel::Attribute itemStyle; + itemStyle.textAlign = TextAlign(TextAlign_Left | TextAlign_VCenter); + m_xmlui->AddFontStyle(L"listitem", itemStyle); + + std::wstring xml = LR"( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )"; + + bool ret = m_xmlui->CreateUIFromXML(root_control, xml); + if (!ret) + MessageBoxW(nullptr, L"创建UI失败!", L"error", MB_ICONERROR); +} + +void MainWindow::WriteRes(UIControl* control) +{ + std::vector selfile; + if (FS::UI::MBrowseForFile(true, false, { {L"AllFiles (*.*)", L"*.*"} }, (_m_param)g_hWnd, selfile)) + { + UIResource files = dmRes.ReadFiles(selfile[0]); + if (files.data) + { + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + UIEditBox* reskey = static_cast(GetRootControl()->Child(L"reskey")); + UICheckBox* isblock = static_cast(GetRootControl()->Child(L"isblock")); + std::wstring resname = listbox->GetItem(listbox->GetCurSelItem())->GetText().data(); + + DMResKey newKey; + newKey.res = files; + newKey.block = isblock->GetSel(); + if(newKey.block) + newKey.blockSize = std::stoi(static_cast(GetRootControl()->Child(L"blocksize"))->GetCurText()); + + if (newKey.block) + EnableWindow(false); + + DMEncBlockCallback callback; + if (newKey.block) + { + auto param = new std::pair>; + param->first = this; + param->second.first = (UIProgressBar*)GetRootControl()->Child(L"progress"); + param->second.first->SetVisible(true); + param->second.second = files; + callback.callback = blockcallback; + callback.param = (_m_param)param; + } + + if (dmRes.ChangeResource(resname.c_str(), newKey, reskey->GetCurText().c_str(), DataRes, callback)) + { + if (!newKey.block) { + files.Release(); + WriteSuccess(newKey.res.size); + } + } + else { + files.Release(); + EnableWindow(true); + MessageBoxW(g_hWnd, L"无法将资源写入资源内存!.", L"写入资源", MB_ICONERROR); + } + } + else + MessageBoxW(g_hWnd, L"文件读取失败!无法写入资源.", L"写入资源", MB_ICONERROR); + } +} + +void MainWindow::WriteSuccess(_m_size ressize) +{ + GetRootControl()->Child(L"importres")->SetEnabled(false); + GetRootControl()->Child(L"isblock")->SetEnabled(false); + GetRootControl()->Child(L"blocksize")->SetEnabled(false); + GetRootControl()->Child(L"importres")->SetEnabled(false); + GetRootControl()->Child(L"cleanres")->SetEnabled(true); + GetRootControl()->Child(L"exportres")->SetEnabled(true); + + ShowResSize(ressize); + SetWindowTextW(g_hWnd, ::m_title.c_str()); + + MessageBoxW(g_hWnd, L"写入资源成功!", L"写入资源", MB_ICONINFORMATION); +} + +void MainWindow::SaveRes(UIControl* control) +{ + std::vector selfile; + if (FS::UI::MBrowseForFile(false, false, { {L"AllFiles (*.*)", L"*.*"} }, (_m_param)g_hWnd, selfile)) + { + UIListBox* listbox = (UIListBox*)GetRootControl()->Child(L"reslist"); + UIEditBox* reskey = static_cast(GetRootControl()->Child(L"reskey")); + std::wstring resname = listbox->GetItem(listbox->GetCurSelItem())->GetText().data(); + + _m_size blocksize = 0; + if (dmRes.ReadResBlockInfo(resname, &blocksize)) + { + _m_size cur = 0; + _m_size size = 0; + _m_size allsize = dmRes.ReadResourceSize(resname, DataRes); + std::wstring key = reskey->GetCurText(); + auto progress = (UIProgressBar*)GetRootControl()->Child(L"progress"); + progress->SetCurValue(0); + progress->SetVisible(true, true); + + FILE* file; + _wfopen_s(&file, selfile[0].data(), L"wb"); + EnableWindow(false); + for (;;) + { + if (!file) { + MessageBoxW(g_hWnd, L"写出资源失败! 无法写出到磁盘", L"写出数据", MB_ICONERROR); + break; + } + + _m_size ret = 0; + UIResource res = dmRes.ReadResourceBlock(resname, key, cur, blocksize, &ret); + + if(ret) + fwrite(res.data, 1, ret, file); + + res.Release(); + + cur += blocksize; + size += ret; + _m_size value = _m_size((double)size / (double)allsize * 100.f); + if (value != progress->GetCurValue()) + progress->SetCurValue(value); + if (size == allsize) { + fclose(file); + MessageBoxW(g_hWnd, L"写出资源成功!", L"写出资源", MB_ICONINFORMATION); + break; + } + Settings::UIMessageLoop(); + } + EnableWindow(true); + progress->SetVisible(false, true); + } + else { + DMResKey key = dmRes.ReadResource(resname, reskey->GetCurText(), DataRes); + + if (dmRes.WriteFiles(selfile[0], key.res)) + MessageBoxW(g_hWnd, L"写出资源成功!", L"写出资源", MB_ICONINFORMATION); + else + MessageBoxW(g_hWnd, L"写出资源失败! 无法写出到磁盘", L"写出数据", MB_ICONERROR); + key.res.Release(); + } + } +} + +void MainWindow::OnSelChange(UIControl* control) +{ + UIListBox* listbox = (UIListBox*)control; + UIEditBox* resName = static_cast(GetRootControl()->Child(L"resname")); + std::wstring resname = listbox->GetItem(curSelItem)->GetText().data(); + resName->SetCurText(resname); + resName->SetEnabled(true); + + auto block = (UICheckBox*)GetRootControl()->Child(L"isblock"); + _m_size blocksize = 1024; + bool isblock = dmRes.ReadResBlockInfo(resname, &blocksize); + if (!isblock) + blocksize = 1024; + block->SetSel(isblock); + block->SetEnabled(false); + auto blocksizeedit = (UIEditBox*)GetRootControl()->Child(L"blocksize"); + blocksizeedit->SetCurText(std::to_wstring(blocksize)); + blocksizeedit->SetEnabled(false); + + GetRootControl()->Child(L"group")->SetEnabled(true); + GetRootControl()->Child(L"decoderes")->SetEnabled(true); + GetRootControl()->Child(L"upresname")->SetEnabled(false); + GetRootControl()->Child(L"exportres")->SetEnabled(false); + GetRootControl()->Child(L"importres")->SetEnabled(false); + GetRootControl()->Child(L"cleanres")->SetEnabled(false); + GetRootControl()->Child(L"importstr")->SetEnabled(false); + GetRootControl()->Child(L"exportstr")->SetEnabled(false); + GetRootControl()->Child(L"cleanstr")->SetEnabled(false); + GetRootControl()->Child(L"savestr")->SetEnabled(false); + GetRootControl()->Child(L"delres")->SetEnabled(true); + UIEditBox* resEdit = static_cast(GetRootControl()->Child(L"strpreview")); + resEdit->SetCurText(L""); + resEdit->SetEnabled(false); + ShowResSize(dmRes.ReadResourceSize(resname, DataRes)); + ShowStrLength(dmRes.ReadResourceSize(resname, StringRes)); +} + +void MainWindow::ShowResSize(_m_size size) +{ + std::wstring size_str, uint; + + double i = pow((double)2, 10); + + if (size < pow((double)2, 10))//dwSize < 1024 + { + size_str = std::to_wstring(size); + uint = L"B"; + } + else if (pow((double)2, 10) <= size && size < pow((double)2, 20))// 1024 <= dwSize < 1024*1024 + { + float fSize = (float)(size * 100 / 1024) / 100; + size_str = std::to_wstring(fSize); + uint = L"KB"; + } + else if (pow((double)2, 20) <= size && size < pow((double)2, 30))// 1024*1024 <= dwSize < 1024*1024*1024 + { + float fSize = (float)(size / 1024 * 100 / 1024) / 100; + size_str = std::to_wstring(fSize); + uint = L"MB"; + } + else if (pow((double)2, 30) <= size && size < pow((double)2, 40)) // 1024*1024*1024 <= dwSize < 1024*1024*1024*1024 + { + float fSize = (float)(size / 1024 * 100 / 1024 / 1024) / 100; + size_str = std::to_wstring(fSize); + uint = L"GB"; + } + GetRootControl()->Child(L"ressize")->SetAttribute( + L"text", L"字节大小:" + size_str.substr(0, size_str.find(L'.') + 3) + uint); +} + +void MainWindow::ShowStrLength(_m_size lenght) +{ + GetRootControl()->Child(L"strsize")->SetAttribute(L"text", L"文本长度:" + std::to_wstring(lenght)); +} + +void blockcallback(_m_size size, _m_size allsize, _m_param param) +{ + auto param_ = (std::pair>*)param; + _m_size value = _m_size((double)size / (double)allsize * 100.f); + if (value != param_->second.first->GetCurValue()) + param_->second.first->SetCurValue(value); + if (size == allsize) { + param_->second.second.Release(); + param_->first->WriteSuccess(size); + param_->second.first->SetVisible(false, true); + param_->first->EnableWindow(true); + delete param_; + } +} +#else +#error "需要启用DmResFileV1 (MUI_CFG_ENABLE_V1DMRES宏)在Mui_Config.h" +#endif \ No newline at end of file diff --git a/DmResEditor/DmResEditor.h b/DmResEditor/DmResEditor.h new file mode 100644 index 0000000..06ce0eb --- /dev/null +++ b/DmResEditor/DmResEditor.h @@ -0,0 +1,36 @@ +// 8-12-2020 Create +#pragma once +#include "resource.h" +#include "Mui.h" + +extern HINSTANCE m_hInstance; + +using namespace Mui; + +class MainWindow : public Window::UIWindowsWnd +{ +public: + MainWindow(Render::MRender* pRender) : UIWindowsWnd(pRender) {} + + bool AfterCreated(); + virtual bool EventProc(UINotifyEvent event, Ctrl::UIControl* control, _m_param param) override; + virtual _m_result EventSource(MEventCodeEnum code, _m_param param) override; + +private: + void CreateControls(); + //写入资源 + void WriteRes(Ctrl::UIControl* control); + void WriteSuccess(_m_size ressize); + //写出资源 + void SaveRes(Ctrl::UIControl* control); + //当前选中表项更改 + void OnSelChange(Ctrl::UIControl* control); + //显示文件大小 + void ShowResSize(_m_size size); + //显示文本长度 + void ShowStrLength(_m_size lenght); + + int curSelItem = 0; + + friend void blockcallback(_m_size size, _m_size allsize, _m_param param); +}; \ No newline at end of file diff --git a/DmResEditor/DmResEditor.ico b/DmResEditor/DmResEditor.ico new file mode 100644 index 0000000..b3ec03b Binary files /dev/null and b/DmResEditor/DmResEditor.ico differ diff --git a/DmResEditor/DmResEditor.rc b/DmResEditor/DmResEditor.rc new file mode 100644 index 0000000..7b985cf Binary files /dev/null and b/DmResEditor/DmResEditor.rc differ diff --git a/DmResEditor/DmResEditor.vcxproj b/DmResEditor/DmResEditor.vcxproj new file mode 100644 index 0000000..c420063 --- /dev/null +++ b/DmResEditor/DmResEditor.vcxproj @@ -0,0 +1,202 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {9d0d532d-c306-47ca-b438-88a0408831d1} + DmResEditor + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)bin\$(ProjectName)\Debug\ + $(SolutionDir)$(ProjectName)\Cache\Debug\ + + + false + $(SolutionDir)bin\$(ProjectName)\Release\ + $(SolutionDir)$(ProjectName)\Cache\Release\ + + + true + $(SolutionDir)bin\$(ProjectName)\Debug64\ + $(SolutionDir)$(ProjectName)\Cache\Debug64\ + + + false + $(SolutionDir)bin\$(ProjectName)\Release64\ + $(SolutionDir)$(ProjectName)\Cache\Release64\ + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreadedDebug + $(SolutionDir)MiaoUI\src\include;%(AdditionalIncludeDirectories) + stdcpp17 + + + Windows + true + $(SolutionDir)MiaoUI\library\;%(AdditionalLibraryDirectories) + + + PerMonitorHighDPIAware + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + $(SolutionDir)MiaoUI\src\include;%(AdditionalIncludeDirectories) + stdcpp17 + + + Windows + true + true + true + $(SolutionDir)MiaoUI\library\;%(AdditionalLibraryDirectories) + + + PerMonitorHighDPIAware + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreadedDebug + $(SolutionDir)MiaoUI\src\include;%(AdditionalIncludeDirectories) + stdcpp17 + + + Windows + true + $(SolutionDir)MiaoUI\library\;%(AdditionalLibraryDirectories) + + + false + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + $(SolutionDir)MiaoUI\src\include;%(AdditionalIncludeDirectories) + stdcpp17 + + + Windows + true + true + true + $(SolutionDir)MiaoUI\library\;%(AdditionalLibraryDirectories) + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DmResEditor/DmResEditor.vcxproj.filters b/DmResEditor/DmResEditor.vcxproj.filters new file mode 100644 index 0000000..c4c28d6 --- /dev/null +++ b/DmResEditor/DmResEditor.vcxproj.filters @@ -0,0 +1,67 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + + + 资源文件 + + + + + 资源文件 + + + 资源文件 + + + 资源文件 + + + 资源文件 + + + 资源文件 + + + 资源文件 + + + 资源文件 + + + 资源文件 + + + 资源文件 + + + \ No newline at end of file diff --git a/DmResEditor/DmResEditor.vcxproj.user b/DmResEditor/DmResEditor.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/DmResEditor/DmResEditor.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/DmResEditor/framework.h b/DmResEditor/framework.h new file mode 100644 index 0000000..f8f8bf7 --- /dev/null +++ b/DmResEditor/framework.h @@ -0,0 +1,14 @@ +// header.h: 标准系统包含文件的包含文件, +// 或特定于项目的包含文件 +// + +#pragma once +#include +#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容 +// Windows 头文件 +#include +// C 运行时头文件 +#include +#include +#include +#include diff --git a/DmResEditor/res/button.png b/DmResEditor/res/button.png new file mode 100644 index 0000000..c211475 Binary files /dev/null and b/DmResEditor/res/button.png differ diff --git a/DmResEditor/res/checkbox.png b/DmResEditor/res/checkbox.png new file mode 100644 index 0000000..b4e73d0 Binary files /dev/null and b/DmResEditor/res/checkbox.png differ diff --git a/DmResEditor/res/edit.png b/DmResEditor/res/edit.png new file mode 100644 index 0000000..fd17d38 Binary files /dev/null and b/DmResEditor/res/edit.png differ diff --git a/DmResEditor/res/listbox.png b/DmResEditor/res/listbox.png new file mode 100644 index 0000000..eb3443d Binary files /dev/null and b/DmResEditor/res/listbox.png differ diff --git a/DmResEditor/res/listitem.png b/DmResEditor/res/listitem.png new file mode 100644 index 0000000..019a785 Binary files /dev/null and b/DmResEditor/res/listitem.png differ diff --git a/DmResEditor/res/progress.png b/DmResEditor/res/progress.png new file mode 100644 index 0000000..915a93a Binary files /dev/null and b/DmResEditor/res/progress.png differ diff --git a/DmResEditor/res/scroll.png b/DmResEditor/res/scroll.png new file mode 100644 index 0000000..11e60d2 Binary files /dev/null and b/DmResEditor/res/scroll.png differ diff --git a/DmResEditor/res/wnd.psd b/DmResEditor/res/wnd.psd new file mode 100644 index 0000000..6a64220 Binary files /dev/null and b/DmResEditor/res/wnd.psd differ diff --git a/DmResEditor/resource.h b/DmResEditor/resource.h new file mode 100644 index 0000000..b840055 --- /dev/null +++ b/DmResEditor/resource.h @@ -0,0 +1,29 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ 生成的包含文件。 +// 供 DmResEditor.rc 使用 +// +#define IDC_MYICON 2 +#define IDD_DMRESEDITOR_DIALOG 102 +#define IDI_DMRESEDITOR 107 +#define IDI_SMALL 108 +#define IDR_MAINFRAME 128 +#define IDB_PNG1 129 +#define IDB_PNG2 130 +#define IDB_PNG3 131 +#define IDB_PNG4 132 +#define IDB_PNG5 133 +#define IDB_PNG6 134 +#define IDB_PNG7 135 +#define IDC_STATIC -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 136 +#define _APS_NEXT_COMMAND_VALUE 32771 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif diff --git a/DmResEditor/small.ico b/DmResEditor/small.ico new file mode 100644 index 0000000..b3ec03b Binary files /dev/null and b/DmResEditor/small.ico differ diff --git a/MiaoUI/.editorconfig b/MiaoUI/.editorconfig new file mode 100644 index 0000000..86bd7f4 --- /dev/null +++ b/MiaoUI/.editorconfig @@ -0,0 +1,6 @@ +# Visual Studio 生成了具有 C++ 设置的 .editorconfig 文件。 +root = true + +[*.{c++,cc,cpp,cppm,cu,cuh,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] +end_of_line = lf # 行尾 UNIX 格式 LF +charset = utf-8-bom \ No newline at end of file diff --git a/MiaoUI/AttribName.txt b/MiaoUI/AttribName.txt new file mode 100644 index 0000000..6c6b280 --- /dev/null +++ b/MiaoUI/AttribName.txt @@ -0,0 +1,273 @@ +/** + * Info: XML属性名列表 + * Author: Maple + * + * date: 2021-11-9 Create + * @winmoes.com + */ + + +属性组: +PropGroup + id //属性组ID文本 + xxx = xxx //任意属性和属性值 + .... +例: + + //控件将拥有属性组的属性 frame和text + +默认属性组: +通过函数AddDefPropGroup可以添加全局默认属性组 +xml示例: + + + + + +这为UIButton、UICheckBox、UISlider设置了默认属性 +使用CreateUIFromXML创建控件时 即使不写属性 也包含这些默认属性 +默认属性组中也可以引用普通属性组 实现属性的快速设置 + +字符串表: +可以通过函数AddStringList添加默认字符串列表 + +设置控件属性时 以 '#' 开头后面跟字符串名称 即可引用此字符串 + +例如 AddStringList(L"测试", L"目标字符串"); +创建控件 + +控件文本将为 "目标字符串" + +同时 如果使用SetStringValue函数修改字符串 那么与之关联的所有控件文本都将发生改变 + +可以利用此特性实现多语言文本快速切换或者样式组的快速切换 + +UIBitmap属性如果为普通名称则读取Image、Bitmap类型资源 如果为 "@svg:" 开头则读取SVGImg资源 可追加颜色替换参数 如 "@svg:testimg,0,0,0,255"或"@svg:testimg,@hex:FFFFFFFF" +_m_color属性直接使用RGBA或RGB颜色值 如 "255,255,255,255" 如果要使用十六进制则写为 "@hex:FFFFFFFF" 格式为RRGGBBAA 或者RRGGBB 不写Alpha默认255或FF + +控件类型: + +xx - 代表参数数量 如 4x = 111,111,111,111 +bool - 代表文本只能是 true 或 false + +xxx : xxx : 代表还有属性继承自xxx控件 + +style* - UIStyle指针 如果使用MuiXML 则为资源文件的名称 +UILabelConfig* - 代表字体样式结构体指针 如果使用MuiXML 为AddFontStyle所添加到字体列表的名称 +UIResource* - 代表资源结构体指针 如果使用MuiXML 为资源文件中的资源名称 + +UIControl: + pos 2x - 控件位置 左 顶 + size 2x - 控件尺寸 宽 高 可为百分比 代表使用父窗口的宽度百分比 例如 50%,50% 或者结尾单位为f 50f 代表使用FillMinus布局参数 即相当于 对于父窗口尺寸 - 50 + frame 4x - 控件框架(如果使用此属性 则代替前两个属性) 左 顶 宽 高 + - 宽 高 可为百分比 代表使用父窗口的宽度百分比 例如 50%,50% + autoSize (bool) - 控件尺寸是否根据子内容自动计算 默认false 这将忽略size和frame的宽高属性值 + minSize - 控件最小尺寸 即使开启autoSize 也会限制 + maxSize - 控件最大尺寸 即使开启autoSize 也会限制 值为-1则无限制 + padding 4x - 内边距 + name - 控件名称 + data (_m_param) - 用户自定义数据 + visible (bool) - 是否可见 + enable (bool) - 是否可用 + alpha (_m_byte) - 不透明度 0-255 + enableFocus (bool) - 是否接受焦点 + msgFilter (bool) - 是否穿透消息 + msgLgnore (bool) - 是否忽略消息 + bgColor 4x - 背景颜色 R,G,B,A + frameColor 4x - 边框颜色 R,G,B,A + frameWidth (int) - 边框宽度 只能是数字 + frameRound (float) - 圆角度 + align (int) - 对齐方式 可以是枚举数字 如 1代表 UIAlignment_Block 也可以是字符串 如 Block、LinearV..... + dpiScale (bool) - 单独设置此控件是否支持DPI缩放 (如果窗口dpi缩放关闭 则此选项也不会有效) + scale 4x (float) - 设置控件缩放比例 X Y W H 默认 1.0,1.0,1.0,1.0 + shadowColor (4x) - 阴影颜色 + shadowOffset (2x) - 阴影偏移位置 + shadowExtend (int) - 阴影扩展 px + shadowRadius (float) - 阴影模糊半径 + +以下控件都具有UIControl的属性 控件名称排序按A-Z + +//该控件autoSize 默认 true +UIButton: UILabel : + * style (UIStyle*) + * animate (bool) - 是否启用动画效果 + * aniAlphaType (bool) - 动画效果方式 混合或覆盖 + * inset (_m_rect_t) - 内边距 仅autoSize=true 时才有效 + +//该控件autoSize 默认 true +UICheckBox: UILabel : + * style (UIStyle*) + * isSel (bool) - 是否被选中 + * allowClick (bool) - 是否可通过点击改变选中状态(否则只能手动设置isSel来控制是否选中) + * animate (bool) - 是否启用动画效果 + * aniAlphaType (bool) - 动画效果方式 混合或覆盖 + * textOffset (int) - 文本和图标之间的距离 + +UIColorPicker: + * color 4x - RGBA颜色 + * hsv 3x - HSV颜色 + +//该控件autoSize 默认 true +UIComBox: UILabel + * style (UIStyle*) + * dropIcon (UIBitmap*) - 下拉列表图标 + * dropIconXPos (int) - 下拉列表图标X偏移位置 + * dropIconAutoPos (bool) - 自动计算下拉列表图标位置 + * popTop (bool) - 向上弹出菜单 + * menuHeight (int) - 弹出菜单高度(px) + * listShadowBlur (float) - 弹出列表阴影模糊度 默认6.f + * listShadowColor (_m_color) 4x - 弹出列表阴影颜色 默认RGBA(0,0,0,50) + * listShadowOffset (UIPoint) 2x - 弹出列表阴影偏移位置 x,y 默认 0,5 + * listShadowExtend (int) - 弹出列表阴影扩展 默认0 + * 列表属性: + * listStyle (UIStyle*) - 列表样式 + * itemStyle (UIStyle*) - 列表项样式 + * itemHeight (int) - 列表项高度(px) + * lineSpace (int) - 列表行间距(px) + * iFontStyle (UIListBox::ItemFont) - 列表项字体默认样式 + * iFont (std::wstring_view) - 列表项字体默认样式 - 字体名称 + * iFontSize (_m_ushort) - 列表项字体默认样式 - 字体尺寸 默认12px + * iFontBold (bool) - 列表项字体默认样式 - 是否粗体 + * iFontItalics (bool) - 列表项字体默认样式 - 是否斜体 + * iFontUnderline (bool) - 列表项字体默认样式 - 是否下划线 + * iFontStrikeout (bool) - 列表项字体默认样式 - 是否删除线 + * iFontColor 4x - 列表项字体默认样式 - 字体颜色 默认RGBA(0,0,0,255) + * iTextAlign - 列表项字体默认样式 - 文本对齐方式 仅数字 为TextAlignment枚举值 默认LeftTop + * iFontCustom (_m_param) - 列表项字体默认样式 - 自定义字体集对象参数 + * drawOffset (UIPoint) 2x - 列表内容绘制偏移参数(px) + * 列表滚动条属性: + * styleV (UIStyle*) - 垂直滚动条样式 + * animate (bool) - 是否启用动画效果 + * button (bool) - 是否显示滚动条上下调节按钮 + * dragValueV (int) - 垂直滚动条当前值 + * barWidth (int) - 滚动条宽度 + * barMinHeight (int) - 滚动条按钮最小高(宽)度 + * btnHeight (int) - 滚动条上下调节按钮高度 + * inset (_m_rect_t) 4x - 内边距 + +UIEditBox: UIScroll + * style (UIStyle*) + * fontStyle (UILabel::Attribute*) - 字体样式 + * text (std::wstring_view) - 当前文本 + * multiline (bool) - 是否为多行编辑框 + * password (bool) - 是否为密码输入模式 + * passChar (wchar_t) - 密码遮掩符 + * readOnly (bool) - 是否为只读模式 + * isRich (bool) - 是否为富文本模式 + * wordWrap (bool) - 是否自动折行 + * wordAutoSel (bool) - 是否自动选择文本 + * number (bool) - 是否为数字输入模式 + * scroll (bool) - 是否有滚动条 + * autoBar (bool) - 自动显示滚动条 + * limitText (int) - 最大可输入文本数 + * editAlign (_m_word) - 编辑框布局样式 ES_XXX + * caretColor (_m_color) - 指针颜色 + * pholderText (std::wstring_view) - 占位符文本 + * pholderTextColor (_m_color) - 占位符文本颜色 + +//该控件autoSize 默认 true +UIImgBox: + * img (UIBitmap*) + * imgStyle //图片对齐方式 仅数字 为UIImageBoxStyle的枚举值 + +//该控件autoSize 默认 true +UILabel: + * fontStyle (UILabel::Attribute*) + * text (std::wstring_view) - 文本 + * font (std::wstring_view) - 字体名称 + * fontSize (_m_ushort) - 字体尺寸 默认12px + * fontBold (bool) - 是否粗体 + * fontItalics (bool) - 是否斜体 + * fontUnderline (bool) - 是否下划线 + * fontStrikeout (bool) - 是否删除线 + * fontColor 4x - 字体颜色 默认RGBA(0,0,0,255) + * hyperlink (bool) - 是否为超链接 + * url (std::wstring_view) - 超链接网址 + * urlColor 4x - 超链接热点颜色 默认RGBA(167,226,54,255) + * textAlign - 文本对齐方式 仅数字 为TextAlign的枚举值组合 默认LeftTop + * fontCustom (_m_ptrv) - 自定义字体集对象参数 + * shadowUse (bool) - 使用文本阴影效果 + * shadowBlur (float) - 文本阴影模糊度 默认1.f + * shadowColor (_m_color) 4x - 文本阴影颜色 默认RGBA(0,0,0,255) + * shadowOffset (UIPoint) 2x - 文本阴影偏移位置 x,y 默认 1,1 + * shadowLow (bool) - 是否使用低质量阴影 如果为true=没有模糊效果 blur属性无效 + +UIListBox: UIScroll + * style (UIStyle*) + * itemStyle (UIStyle*) - 列表项样式 + * itemHeight (int) - 列表项高度(px) + * lineSpace (int) - 列表行间距(px) + * iFontStyle (UIListBox::ItemFont) - 列表项字体默认样式 + * iFont (std::wstring_view) - 列表项字体默认样式 - 字体名称 + * iFontSize (_m_ushort) - 列表项字体默认样式 - 字体尺寸 默认12px + * iFontBold (bool) - 列表项字体默认样式 - 是否粗体 + * iFontItalics (bool) - 列表项字体默认样式 - 是否斜体 + * iFontUnderline (bool) - 列表项字体默认样式 - 是否下划线 + * iFontStrikeout (bool) - 列表项字体默认样式 - 是否删除线 + * iFontColor 4x - 列表项字体默认样式 - 字体颜色 默认RGBA(0,0,0,255) + * iTextAlign - 列表项字体默认样式 - 文本对齐方式 仅数字 为TextAlignment枚举值 默认LeftTop + * iFontCustom (_m_param) - 列表项字体默认样式 - 自定义字体集对象参数 + * drawOffset (UIPoint) 2x - 列表内容绘制偏移参数(px) + * allowRightSel (bool) - 允许右键点击也能选中列表项目 + +//该控件autoSize 默认 true +UINavBar: + * fontStyle (UILabel::Attribute*) - 字体样式 + * font (std::wstring_view) - 字体 + * fontColor (_m_color) 4x - 字体颜色 默认RGBA(120,120,120,255) + * fontHoverColor (_m_color) 4x - 字体颜色 Hover状态 默认RGBA(0,0,0,255) + * fontPressColor (_m_color) 4x - 字体颜色 按下状态 默认RGBA(150,150,150,255) + * fontSize (_m_ushort) - 字体尺寸 默认12px + * fontBold (bool) - 是否粗体 + * fontItalics (bool) - 是否斜体 + * fontUnderline (bool) - 是否下划线 + * fontStrikeout (bool) - 是否删除线 + * fontCustom (_m_ptrv) - 自定义字体集对象参数 + * shadowUse (bool) - 使用文本阴影效果 + * shadowBlur (float) - 文本阴影模糊度 默认1.f + * shadowColor (_m_color) 4x - 文本阴影颜色 默认RGBA(0,0,0,255) + * shadowOffset (UIPoint) 2x - 文本阴影偏移位置 x,y 默认 1,1 + * shadowLow (bool) - 是否使用低质量阴影 如果为true=没有模糊效果 blur属性无效 + * itemSpace (int) - item间距 + * barSpace (int) - 横条和文字间距 + * barHeight (int) - 横条高度 默认4px + * barAnitime (int) - 横条过渡动画时长(ms) 默认300ms + * barRound (float) - 横条圆角度 默认1.5f + * barColor (_m_color) 4x - 横条颜色 + +UIProgressBar: + * style (UIStyle*) + * maxValue (int) - 最大值 + * value (int) - 当前值 + * leftShow (bool) - 如果非垂直 进度从左往右显示 (默认true) + * bottomShow (bool) - 如果非水平 进度从下往上显示 (默认true) + +UIScroll: + * styleV (UIStyle*) - 垂直滚动条样式 + * styleH (UIStyle*) - 水平滚动条样式 + * vertical (bool) - 显示垂直滚动条 + * horizontal (bool) - 显示水平滚动条 + * active (bool) - 始终显示滚动条(无效) + * animate (bool) - 是否启用动画效果 + * button (bool) - 是否显示滚动条上下调节按钮 + * range (UISize) - 滚动条最大可滚动区域 + * rangeV (int) - 垂直滚动条最大可滚动区域 + * rangeH (int) - 水平滚动条最大可滚动区域 + * dragValue (UISize) - 滚动条当前值 + * dragValueV (int) - 垂直滚动条当前值 + * dragValueH (int) - 水平滚动条当前值 + * barWidth (int) - 滚动条宽度 + * barMinHeight (int) - 滚动条按钮最小高(宽)度 + * btnHeight (int) - 滚动条上下调节按钮高度 + * inset (_m_rect_t) - 内边距 + +UISlider: + * style (UIStyle*) + * btnStyle (UIStyle*) - 滑块按钮样式 + * maxValue (int) - 最大值 + * minValue (int) - 最小值 + * value (int) - 当前值 + * leftShow (bool) - 如果非垂直 进度从左往右显示 (默认true) + * bottomShow (bool) - 如果非水平 进度从下往上显示 (默认true) + * trackInset (_m_rect_t) 4x - 轨道内边距 + * btnSize (UISize) 2x - 拖拽按钮尺寸 0=自动计算 \ No newline at end of file diff --git a/MiaoUI/COPYING b/MiaoUI/COPYING new file mode 100644 index 0000000..e72bfdd --- /dev/null +++ b/MiaoUI/COPYING @@ -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/MiaoUI/COPYING.LESSER b/MiaoUI/COPYING.LESSER new file mode 100644 index 0000000..153d416 --- /dev/null +++ b/MiaoUI/COPYING.LESSER @@ -0,0 +1,165 @@ + GNU LESSER 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. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/MiaoUI/GeometryXML.txt b/MiaoUI/GeometryXML.txt new file mode 100644 index 0000000..9ff9427 --- /dev/null +++ b/MiaoUI/GeometryXML.txt @@ -0,0 +1,32 @@ +通过AddGeometryStyle创建UIStyle时 需要提供的XML格式 + +每个part部分代表一个控件状态 +例如按钮有4个状态 第一个状态为普通第二为Hover第三为按下第四为禁用状态 + +可用命令: +fill_rect - 填充矩形 参数: rc、color +fill_round - 填充圆角矩形 参数: rc、color、value +fill_ellipse - 填充椭圆 参数: rc、color +draw_rect - 绘制矩形边框 参数: rc、color +draw_round - 绘制圆角矩形边框 参数: rc、color、value、width +draw_line - 绘制一条线 参数: rc、color、width +draw_ellipse - 绘制椭圆边框 参数: rc、color、width + +参数介绍: +rc - 为相对于目标的内边距 left、top、right、bottom +例如原始目标为 100,100,200,200 rc为2,2,2,2 最终绘制为 102,102,198,198 +可以使用l|t|r|b进行相对计算 例如 原始目标为 100,100,200,200 rc为 2,b2,2,0 最终绘制为 102, 198, 198, 200 +l=left t=top r=right b=bottom + +color - 为颜色RGBA值 +value - 为圆角度 float类型 +width - 为线宽度 uint类型 + + + + + + + ........... + +.......... \ No newline at end of file diff --git a/MiaoUI/MiaoUI.vcxproj b/MiaoUI/MiaoUI.vcxproj new file mode 100644 index 0000000..6c4dd3d --- /dev/null +++ b/MiaoUI/MiaoUI.vcxproj @@ -0,0 +1,327 @@ + + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 16.0 + Win32Proj + {2f7059f0-b4a9-4e52-a553-eb0667880680} + MiaoUI + 10.0 + MiaoUILite + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + true + v143 + Unicode + true + + + StaticLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)MiaoUI\library\ + $(SolutionDir)MiaoUI\Cache\Debug\ + $(ProjectName)32d + + + false + $(SolutionDir)MiaoUI\library\ + $(SolutionDir)MiaoUI\Cache\Release\ + $(ProjectName)32 + + + true + $(SolutionDir)MiaoUI\library\ + $(SolutionDir)MiaoUI\Cache\Debug64\ + $(ProjectName)64d + false + false + + + false + $(SolutionDir)MiaoUI\library\ + $(SolutionDir)MiaoUI\Cache\Release64\ + $(ProjectName)64 + + + + Level3 + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + Default + MultiThreadedDebug + true + $(SolutionDir);$(SolutionDir)MiaoUI\src\include;$(SolutionDir)ThirdParty\libvorbis-1.3.7\include;$(SolutionDir)ThirdParty\libogg-1.3.5\include;$(SolutionDir)ThirdParty\cryptopp8.6;$(SolutionDir)ThirdParty\freetype-2.11.1\include;$(SolutionDir)ThirdParty\glew-2.2.0\include;$(SolutionDir)ThirdParty\ffmpeg-4.2.1\include;$(SolutionDir)ThirdParty\glm-0.9.8 + stdcpp17 + + + + + true + + + $(SolutionDir)ThirdParty\build\static\;%(AdditionalLibraryDirectories) + %(AdditionalDependencies) + + + + + Level3 + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + MultiThreaded + Default + Fast + AnySuitable + Speed + true + true + true + $(SolutionDir);$(SolutionDir)MiaoUI\src\include;$(SolutionDir)ThirdParty\libvorbis-1.3.7\include;$(SolutionDir)ThirdParty\libogg-1.3.5\include;$(SolutionDir)ThirdParty\cryptopp8.6;$(SolutionDir)ThirdParty\freetype-2.11.1\include;$(SolutionDir)ThirdParty\glew-2.2.0\include;$(SolutionDir)ThirdParty\ffmpeg-4.2.1\include;$(SolutionDir)ThirdParty\glm-0.9.8 + stdcpp17 + MaxSpeed + + + + + true + true + true + + + $(SolutionDir)ThirdParty\build\static\;%(AdditionalLibraryDirectories) + %(AdditionalDependencies) + + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + Default + true + MultiThreadedDebug + $(SolutionDir);$(SolutionDir)MiaoUI\src\include;$(SolutionDir)ThirdParty\libvorbis-1.3.7\include;$(SolutionDir)ThirdParty\libogg-1.3.5\include;$(SolutionDir)ThirdParty\cryptopp8.6;$(SolutionDir)ThirdParty\freetype-2.11.1\include;$(SolutionDir)ThirdParty\glew-2.2.0\include;$(SolutionDir)ThirdParty\ffmpeg-4.2.1\include;$(SolutionDir)ThirdParty\glm-0.9.8 + stdcpp17 + + + + + true + + + $(SolutionDir)ThirdParty\build\static\;%(AdditionalLibraryDirectories) + %(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + MultiThreaded + Default + Fast + AnySuitable + Speed + true + true + true + $(SolutionDir);$(SolutionDir)MiaoUI\src\include;$(SolutionDir)ThirdParty\libvorbis-1.3.7\include;$(SolutionDir)ThirdParty\libogg-1.3.5\include;$(SolutionDir)ThirdParty\cryptopp8.6;$(SolutionDir)ThirdParty\freetype-2.11.1\include;$(SolutionDir)ThirdParty\glew-2.2.0\include;$(SolutionDir)ThirdParty\ffmpeg-4.2.1\include;$(SolutionDir)ThirdParty\glm-0.9.8 + stdcpp17 + MaxSpeed + + + + + true + true + true + + + $(SolutionDir)ThirdParty\build\static\;%(AdditionalLibraryDirectories) + %(AdditionalDependencies) + + + + + + + + + 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 + + + + + + \ No newline at end of file diff --git a/MiaoUI/MiaoUI.vcxproj.filters b/MiaoUI/MiaoUI.vcxproj.filters new file mode 100644 index 0000000..4caabf2 --- /dev/null +++ b/MiaoUI/MiaoUI.vcxproj.filters @@ -0,0 +1,322 @@ + + + + + {288c03d6-d041-419e-b33d-61295165f800} + + + {7cac8021-7765-43ec-89c7-c92bcceb64bd} + + + {ad1c4e68-a2e9-4b9b-8093-10df6432f96c} + + + {2967b425-5f6c-4215-b212-55d90fb1b6d8} + + + {590f875b-eb7d-47e8-ad67-318673c74394} + + + {2003f39a-d6d7-4bd8-ac84-7066ab6788ae} + + + {8d0e9cba-05b8-49da-bfab-8ff5fcde7393} + + + {b3027ee6-4d1d-4856-b77c-bec05b7c2332} + + + {757a08fc-930a-4c10-a193-f3af17ba680b} + + + {f2960082-167a-4f7b-9f25-5be08746562d} + + + {d0e525dc-1cee-414c-a619-d8fc13149c01} + + + {48809cc1-0a69-4c67-9098-5193f695f444} + + + {33819f89-b778-4c80-aadd-cadfce3a024b} + + + {4476c774-471d-4a86-8310-cde82d196a1c} + + + {ff780d99-b91e-4b3d-a3bc-2f53fe0adbc7} + + + {ba1db83f-ffc6-4734-8da7-1041adcf3d15} + + + {1d3883b1-aaa8-409c-ab9c-42b0a799ff3f} + + + {2274f20f-90f1-4a5d-909b-61ed9238fd25} + + + {7703827d-0cc6-48eb-aa06-42d5136e532d} + + + {4003d539-05d7-4f64-8a09-625b372a86f8} + + + {5ebd8140-1bb7-42ba-a3ec-f8e1fa8cc4bf} + + + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\Control + + + 头文件\FileSystem + + + 头文件\Render + + + 头文件\Render\Graphs + + + 头文件\Render\Graphs + + + 头文件\Window + + + 头文件\Window + + + ThirdParty\pugixml + + + ThirdParty\pugixml + + + ThirdParty + + + ThirdParty + + + 头文件\Control + + + 头文件\Control + + + 头文件\Render\Node + + + 头文件\Render\Node + + + 头文件\User + + + 头文件 + + + 头文件 + + + 头文件\Render\Node + + + 头文件\Manager + + + 头文件\Manager + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件\FileSystem + + + 头文件\Render\Graphs + + + 头文件\Render\Graphs + + + + + 源文件 + + + 源文件 + + + 源文件 + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\Control + + + 源文件\FileSystem + + + 源文件\Window + + + 源文件\Render\Graphs + + + 源文件\Render + + + ThirdParty\pugixml + + + ThirdParty + + + 源文件\Window + + + 源文件\Render\Graphs + + + 源文件\Control + + + 源文件\Control + + + 源文件\Render\Node + + + 源文件\Render\Node + + + 源文件\User + + + 源文件 + + + 源文件\Render\Node + + + 源文件\Manager + + + 源文件 + + + 源文件\Manager + + + 源文件 + + + 源文件\FileSystem + + + 源文件\Render\Graphs + + + 源文件\Render\Graphs + + + + + + ThirdParty\license + + + + + + + \ No newline at end of file diff --git a/MiaoUI/MiaoUI.vcxproj.user b/MiaoUI/MiaoUI.vcxproj.user new file mode 100644 index 0000000..88a5509 --- /dev/null +++ b/MiaoUI/MiaoUI.vcxproj.user @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/MiaoUI/packages.config b/MiaoUI/packages.config new file mode 100644 index 0000000..43e1ed2 --- /dev/null +++ b/MiaoUI/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/MiaoUI/src/include/Control/Mui_Button.h b/MiaoUI/src/include/Control/Mui_Button.h new file mode 100644 index 0000000..7eed9b5 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_Button.h @@ -0,0 +1,85 @@ +/** + * FileName: Mui_Button.h + * Note: UI按钮控件声明 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-19 Create +*/ + +#pragma once +#include + +namespace Mui::Ctrl +{ + /* UIButton 控件 + * 属性列表: + * style (UIStyle*) + * animate (bool) - 是否启用动画效果 Lite版本无效 + * aniAlphaType (bool) - 动画效果方式 混合或覆盖 Lite版本无效 + * inset (_m_rect_t) - 内边距 仅autoSize=true 时才有效 + * ... + * 还继承UILabel的属性... + */ + class UIButton : public UILabel + { + public: + MCTRL_DEFINE_EX + ( + L"UIButton", + MCTRL_MAKE_ATTRIB({ CtrlMgr::AttribType::UIStyle, L"style" }), + MCTRL_BASE_ATTRIB(UILabel) + ); + + struct Attribute + { + UIStylePtr style = nullptr; + bool animate = false; //Lite版本无效 + bool aniAlphaType = false; //Lite版本无效 + _m_rect_t inset = { 2,2,2,2 }; + }; + + UIButton(UIControl* parent, Attribute attrib, UILabel::Attribute fontStyle = {}); + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + template + bool SetAttributeSrc(std::wstring_view attribName, T&& value, bool draw = true) + { + if (!m_attrib.SetAttribute(attribName, std::forward(value), draw)) + return UILabel::SetAttributeSrc(attribName, std::forward(value), draw); + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + return true; + } + [[nodiscard]] const Attribute& GetAttribute() const; + + protected: + explicit UIButton(Attribute attrib, UILabel::Attribute fontStyle = {}); + + void OnPaintProc(MPCPaintParam param) override; + bool OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) override; + + void ChangeStatus(UIControlStatus&& state, UIControlStatus&& oldState); + + _m_sizef GetContentSize() override; + + private: + CtrlMgr::UIAttribute m_attrib; + + UIControlStatus m_state = UIControlStatus_Normal; + }; +} diff --git a/MiaoUI/src/include/Control/Mui_CheckBox.h b/MiaoUI/src/include/Control/Mui_CheckBox.h new file mode 100644 index 0000000..7984e08 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_CheckBox.h @@ -0,0 +1,104 @@ +/** + * FileName: Mui_CheckBox.h + * Note: UI选择框控件声明 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-11-26 Create +*/ + +#pragma once +#include + +namespace Mui::Ctrl +{ + /* UICheckBox 控件 + * 属性列表: + * style (UIStyle*) + * isSel (bool) - 是否被选中 + * allowClick (bool) - 是否可通过点击改变选中状态(否则只能手动设置isSel来控制是否选中) + * animate (bool) - 是否启用动画效果 Lite版本无效 + * aniAlphaType (bool) - 动画效果方式 混合或覆盖 Lite版本无效 + * textOffset (int) - 文本和图标之间的距离 + * ... + * 还继承UILabel的属性... + */ + class UICheckBox : public UILabel + { + public: + MCTRL_DEFINE_EX + ( + L"UICheckBox", + MCTRL_MAKE_ATTRIB({ CtrlMgr::AttribType::UIStyle, L"style" }), + MCTRL_BASE_ATTRIB(UILabel) + ); + + struct Attribute + { + UIStylePtr style = nullptr; + bool isSel = false; + bool allowClick = true; + bool animate = false; //Lite版本无效 + bool aniAlphaType = false; //Lite版本无效 + int textOffset = 10; + }; + + UICheckBox(UIControl* parent, Attribute attrib, UILabel::Attribute fontStyle = {}); + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + template + bool SetAttributeSrc(std::wstring_view attribName, T&& value, bool draw = true) + { + if (!m_attrib.SetAttribute(attribName, std::forward(value), draw)) + return UILabel::SetAttributeSrc(attribName, std::forward(value), draw); + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + return true; + } + [[nodiscard]] const Attribute& GetAttribute() const; + + virtual void SetSel(bool sel, bool draw = true); + virtual bool GetSel() const; + + protected: + explicit UICheckBox(Attribute attrib, UILabel::Attribute fontStyle = {}); + + enum Status + { + UICheckBoxNormal, //普通 + UICheckBoxHover, //热点 + UICheckBoxPressed, //按下 + UICheckBoxDisbale, //禁止 + UICheckBoxNormalSel, //普通选中 + UICheckBoxHoverSel, //热点选中 + UICheckBoxPressedSel, //按下选中 + UICheckBoxDisbaleSel //禁止选中 + }; + + void OnPaintProc(MPCPaintParam param) override; + bool OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) override; + + _m_sizef GetContentSize() override; + + void ChangeStatus(Status&& state, Status&& oldState); + + private: + CtrlMgr::UIAttribute m_attrib; + + Status m_state = UICheckBoxNormal; + }; +} diff --git a/MiaoUI/src/include/Control/Mui_ColorPicker.h b/MiaoUI/src/include/Control/Mui_ColorPicker.h new file mode 100644 index 0000000..095a9c9 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_ColorPicker.h @@ -0,0 +1,88 @@ +/** + * FileName: Mui_ColorPicker.h + * Note: UI颜色选择器声明 + * + * Copyright (C) 2022-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-9-5 Create +*/ +#pragma once +#include + +namespace Mui::Ctrl +{ + /* UIColorPicker 控件 + * 属性列表: + * color 4x - RGBA颜色 + * hsv 3x - HSV颜色 + */ + class UIColorPicker : public UIControl + { + public: + MCTRL_DEFNAME(L"UIColorPicker"); + MCTRL_DEFSLOT + ( + CtrlMgr::EventSlot<_m_color> changed;//颜色已更改 + ); + + UIColorPicker(UIControl* parent); + + struct HSV + { + _m_ushort hue = 0; + _m_ushort sat = 0; + _m_ushort val = 0; + }; + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + void SetHSVColor(HSV hsv, bool draw = true); + HSV GetHSVColor(); + + void SetRGBAColor(_m_color color, bool draw = true); + _m_color GetRGBAColor(); + + protected: + UIColorPicker() { m_cacheSupport = true; } + + void OnLoadResource(MRenderCmd* render, bool recreate) override; + + void OnPaintProc(MPCPaintParam param) override; + bool OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) override; + bool OnMouseMove(_m_uint flag, const UIPoint& point) override; + bool OnLButtonUp(_m_uint flag, const UIPoint& point) override; + bool OnSetCursor(_m_param hCur, _m_param lParam) override; + + private: + float m_hue = 359.f; + float m_sat = 0.f; + float m_val = 0.f; + + float m_hue_old = m_hue; + + _m_color m_rgb = 0; + _m_color m_rgb_old = 0; + _m_byte m_alpha = 255; + + void UpdateRGB(); + void UpdateHSV(); + + MGradientBrushPtr m_colorBrush = nullptr; + MGradientBrushPtr m_bottomBrush = nullptr; + + bool m_down = false; + }; +} diff --git a/MiaoUI/src/include/Control/Mui_ComBox.h b/MiaoUI/src/include/Control/Mui_ComBox.h new file mode 100644 index 0000000..eb3b6e2 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_ComBox.h @@ -0,0 +1,226 @@ +/** + * FileName: Mui_Combox.h + * Note: UI组合框控件声明 + * + * Copyright (C) 2021-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2021-4-2 Create +*/ + +#pragma once +#include +#include + +namespace Mui::Ctrl +{ + /* UIComBox 控件 + * 属性列表: + * style (UIStyle*) + * dropIcon (UIResource*) - 下拉列表图标 + * dropIconXPos (int) - 下拉列表图标X偏移位置 + * dropIconAutoPos (bool) - 自动计算下拉列表图标位置 + * popTop (bool) - 向上弹出菜单 + * menuHeight (int) - 弹出菜单高度(px) + * listShadowBlur (float) - 弹出列表阴影模糊度 默认6.f + * listShadowColor (_m_color) 4x - 弹出列表阴影颜色 默认RGBA(0,0,0,50) + * listShadowOffset (UIPoint) 2x - 弹出列表阴影偏移位置 x,y 默认 0,5 + * listShadowExtend (int) - 弹出列表阴影扩展 默认0 + * 列表属性: + * listStyle (UIStyle*) - 列表样式 + * itemStyle (UIStyle*) - 列表项样式 + * itemHeight (int) - 列表项高度(px) + * lineSpace (int) - 列表行间距(px) + * iFontStyle (UIListBox::ItemFont) - 列表项字体默认样式 + * iFont (std::wstring_view) - 列表项字体默认样式 - 字体名称 + * iFontSize (_m_ushort) - 列表项字体默认样式 - 字体尺寸 默认12px + * iFontBold (bool) - 列表项字体默认样式 - 是否粗体 + * iFontItalics (bool) - 列表项字体默认样式 - 是否斜体 + * iFontUnderline (bool) - 列表项字体默认样式 - 是否下划线 + * iFontStrikeout (bool) - 列表项字体默认样式 - 是否删除线 + * iFontColor 4x - 列表项字体默认样式 - 字体颜色 默认RGBA(0,0,0,255) + * iTextAlign - 列表项字体默认样式 - 文本对齐方式 仅数字 为TextAlignment枚举值 默认LeftTop + * iFontCustom (_m_param) - 列表项字体默认样式 - 自定义字体集对象参数 + * drawOffset (UIPoint) 2x - 列表内容绘制偏移参数(px) + * 列表滚动条属性: + * styleV (UIStyle*) - 垂直滚动条样式 + * animate (bool) - 是否启用动画效果 + * button (bool) - 是否显示滚动条上下调节按钮 + * dragValueV (int) - 垂直滚动条当前值 + * barWidth (int) - 滚动条宽度 + * barMinHeight (int) - 滚动条按钮最小高(宽)度 + * btnHeight (int) - 滚动条上下调节按钮高度 + * inset (_m_rect_t) 4x - 内边距 + * ... + * 还继承UILabel的属性... + */ + class UIComBox : public UILabel + { + //重写列表框事件 用于失去焦点隐藏列表 + class UIListBoxCom : public UIListBox + { + public: + UIListBoxCom(Attribute attrib, UIScroll::Attribute scrollAttrib = {}) + : UIListBox(std::move(attrib), std::move(scrollAttrib)) + { + HideNode(true, false); + } + + protected: + bool OnWindowMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam) override; + bool SendEvent(UINotifyEvent event, _m_param param) override; + + friend class UIComBox; + UIComBox* combox = nullptr; + }; + public: + MCTRL_DEFINE_EX + ( + L"UIComBox", + MCTRL_MAKE_ATTRIB + ( + { CtrlMgr::AttribType::UIStyle, L"style" }, + { CtrlMgr::AttribType::UIStyle, L"listStyle" }, + { CtrlMgr::AttribType::UIBitmap, L"dropIcon" } + ), + MCTRL_BASE_ATTRIB(UILabel), + MCTRL_BASE_ATTRIB(UIListBox) + ); + + MCTRL_DEFSLOT + ( + CtrlMgr::EventSlot itemClicked;//项目被左键单击 + CtrlMgr::EventSlot itemDBClicked;//项目被左键双击 + CtrlMgr::EventSlot itemChanging;//选中项目即将被更改 设置变量为false则阻止更改 + CtrlMgr::EventSlot itemChanged;//选中项目已被更改 + ); + + struct Attribute + { + UIStylePtr style = nullptr; + UIBitmapPtr dropIcon = nullptr; + int dropIconXPos = 0; + bool dropIconAutoPos = true; + bool popTop = false; + int menuHeight = 300; + + float listShadowBlur = 6.f; + _m_color listShadowColor = 0x32000000; + UIPoint listShadowOffset = { 0, 6 }; + int listShadowExtend = 0; + }; + + UIComBox(UIControl* parent, Attribute attrib, UIListBox::Attribute listAttrib = {}, + UIScroll::Attribute scrollAttrib = {}, UILabel::Attribute fontStyle = {}); + ~UIComBox() override; + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + template + bool SetAttributeSrc(std::wstring_view attribName, T&& value, bool draw = true) + { + if (!m_attrib.SetAttribute(attribName, std::forward(value), this)) + { + if (!UILabel::SetAttributeSrc(attribName, std::forward(value), draw)) + return m_popList->SetAttributeSrc(attribName, std::forward(value), draw); + return true; + } + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + return true; + } + [[nodiscard]] const Attribute& GetAttribute() const; + + /*添加一个列表项目 + * @param item - 自定义列表项目 + * @param index - 列表位置 -1=尾部 + * @param draw - 是否立即绘制 + */ + int AddItem(ListItem* item, int index, bool draw = true); + + /*获取一个列表项目指针 + * @param index - 列表索引 -1=尾部 + * + * @return 成功返回列表指针 失败返回nullptr + */ + ListItem* GetItem(int index) const; + + /*设置当前选中的列表项目 + * @param index - 索引项目 -1=不选中 + * @param draw - 是否立即绘制 + * + * @return 是否成功 若失败则索引值无效 + */ + bool SetCurSelItem(int index, bool draw = true); + + //获取当前选中的列表项目索引 -1=未选中 + int GetCurSelItem() const; + + //获取列表项目总数 + int GetItemListCount() const; + + /*删除一个列表项目 这不会删除ListItem的内存 + * @param index - 列表索引 -1=尾部 + * @param draw - 是否立即绘制 + * + * @return 当前列表项目总数 + */ + int DeleteItem(int index, bool draw = true); + + /*删除一个列表项目 + * @param index - 列表索引 -1=尾部 + * @param delItem - 是否删除列表内存 使用delete + * @param draw - 是否立即绘制 + * + * @return 当前列表项目总数 + */ + int DeleteItem(int index, bool delItem, bool draw = true); + + /*删除所有列表项 + * @param delItem - 是否删除列表内存 + * @param draw - 是否立即绘制 + */ + void DeleteAllItem(bool delItem = true, bool draw = true); + + protected: + explicit UIComBox(Attribute attrib, UIListBox::Attribute listAttrib = {}, + UIScroll::Attribute scrollAttrib = {}, UILabel::Attribute fontStyle = {}); + + void OnPaintProc(MPCPaintParam param) override; + bool OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) override; + bool OnWindowMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam) override; + void OnScale(_m_scale scale) override; + void OnLayoutCalced() override; + + private: + void ShowMenu(bool show); + void CalcMenuFrame(); + void SetText(std::wstring_view text); + bool IsListAttrib(std::wstring_view name); + + CtrlMgr::UIAttribute m_attrib; + + static void BindAttribute(); + + bool m_isHover = false; + + UIControl* m_root = nullptr; + UIListBoxCom* m_popList = nullptr; + + UIControlStatus m_state = UIControlStatus_Normal; + + friend class UIListBoxCom; + }; +} diff --git a/MiaoUI/src/include/Control/Mui_Control.h b/MiaoUI/src/include/Control/Mui_Control.h new file mode 100644 index 0000000..61866eb --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_Control.h @@ -0,0 +1,192 @@ +/** + * FileName: Mui_Control.h + * Note: UI控件基本类型声明 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-17 Create +*/ +#pragma once +#include +#include +#include +#include + +namespace Mui::Ctrl +{ + using namespace Render; + + class UIControl : public UINodeBase + { + public: + UIControl(); + ~UIControl() override; + + //控件类名和注册方法 + static constexpr auto ClassName = L"UIControl"; + static void Register(); + virtual std::wstring GetClsName() const { return ClassName; } + + //设置用户参数 + virtual void SetUserData(_m_param data); + + //获取用户参数 + [[nodiscard]] virtual _m_param GetUserData() const; + + /*设置控件属性 + * @param attribName - 属性名 + * @param attrib - 属性值 + * @param draw - 立即绘制(默认true) + */ + virtual void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true); + + //获取控件属性 + virtual std::wstring GetAttribute(std::wstring_view attribName); + + /*设置控件可用性 + * @param enabled - 可用 + * @param draw - 立即绘制(默认true) + */ + virtual void SetEnabled(bool enabled, bool draw = true); + + //控件是否可用 + [[nodiscard]] virtual bool IsEnabled() const; + + //获取父窗口 + [[nodiscard]] virtual Window::UIWindowBasic* GetParentWin() const; + + template + T* FindChildren(const UIString& name) const + { + return mcast_control(Child(name)); + } + + //FindChildren + [[nodiscard]] UIControl* Child(const UIString& name) const + { + return mcast_control(UINodeBase::FindChildren(name)); + } + + template + T* Child(const UIString& name) const + { + return FindChildren(name); + } + + void AddChildren(UINodeBase* UINode) override; + + //设置控件焦点状态 + virtual void SetFocus(bool focus); + + //设置是否可接受焦点 + virtual void SetEnableFocus(bool enable); + + //设置控件消息穿透 消息向下转发到父窗口 + virtual void SetMsgFilter(bool filter); + + /*设置控件消息忽略 + * 该控件将忽略鼠标以及窗口消息 并向下传递 + * @param ignore - 是否忽略消息 + * @param child - 是否忽略子控件消息 + */ + virtual void SetMsgIgnore(bool ignore, bool child = true); + + /*缩放控件到指定尺寸(使用父窗口缩放比) 会设置Scale + * @param width - 目标宽度 + * @param height - 目标高度 + * @param child - 等比例缩放子控件 + */ + virtual void ScaleControl(_m_uint width, _m_uint height, bool child); + + /*缩放控件到指定比例(使用父窗口缩放比) 会设置Scale + * @param scale - 目标缩放比 + * @param child - 等比例缩放子控件 + */ + virtual void ScaleControl(_m_scale scale, bool child); + + protected: + + //设置消息捕获 + void SetCapture(); + void ReleaseCapture(); + bool isCapture(); + + //设置计时器 + MTimers::ID SetTimer(_m_uint uTimeout); + void KillTimer(MTimers::ID idTimer); + + virtual UIControl* FindMouseControl(const UIPoint& point); + + //获取窗口顶级控件层 + UIControl* GetWindowTopCtrl() const; + UIControl* GetWindowTopCtrl(Window::UIWindowBasic* wnd) const; + + //设置光标状态 + virtual void SetCursor(_m_lpcwstr cursor_name); + + //鼠标和窗口消息 + virtual bool OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam); + virtual bool OnWindowMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam); + //鼠标进入与退出消息 + virtual bool OnMouseEntered(_m_uint flag, const UIPoint& point); + virtual bool OnMouseExited(_m_uint flag, const UIPoint& point); + //鼠标左键消息 + virtual bool OnLButtonDown(_m_uint flag, const UIPoint& point); + virtual bool OnLButtonUp(_m_uint flag, const UIPoint& point); + virtual bool OnLButtonDoubleClicked(_m_uint flag, const UIPoint& point); + //鼠标右键消息 + virtual bool OnRButtonDown(_m_uint flag, const UIPoint& point); + virtual bool OnRButtonUp(_m_uint flag, const UIPoint& point); + virtual bool OnRButtonDoubleClicked(_m_uint flag, const UIPoint& point); + //鼠标中键消息 + virtual bool OnMButtonDown(_m_uint flag, const UIPoint& point); + virtual bool OnMButtonUp(_m_uint flag, const UIPoint& point); + virtual bool OnMButtonDoubleClicked(_m_uint flag, const UIPoint& point); + //鼠标滚轮 + virtual bool OnMouseWheel(_m_uint flag, short delta, const UIPoint& point); + //鼠标移动 + virtual bool OnMouseMove(_m_uint flag, const UIPoint& point); + //设置光标 + virtual bool OnSetCursor(_m_param hCur, _m_param lParam); + + //计时器消息 + virtual void OnTimer(MTimers::ID idTimer); + + //发送事件 + virtual bool SendEvent(UINotifyEvent event, _m_param param = 0); + + //获取调试画笔 + [[nodiscard]] MPenPtr GetDbgFramePen() const; + + //控件数据 + struct ControlData + { + _m_param UserData = 0; //用户参数 + bool Enabled = true; //可用 + bool ParentEnabled = true; //父控件可用 + bool MsgFilter = false; //消息穿透 + bool MsgIgnore = false; //消息忽略 消息将不被处理 控件不会被命中 + bool MsgIgnoreChild = false;//忽略子控件消息 + bool IsFocus = true; //是否接受焦点 + } m_data; + + private: + //派发消息 + bool DispatchMouseMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam); + bool DispatchWindowMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam); + + friend class Window::UIWindowBasic; + }; +} diff --git a/MiaoUI/src/include/Control/Mui_EditBox.h b/MiaoUI/src/include/Control/Mui_EditBox.h new file mode 100644 index 0000000..5399ee5 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_EditBox.h @@ -0,0 +1,530 @@ +/** + * FileName: Mui_EditBox.h + * Note: UI编辑框控件声明 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-28 Create +*/ + +#pragma once +#include +#include + +#ifdef _WIN32 + +#include +#include +#include +#include +#pragma comment(lib, "imm32.lib") +#elif __ANDROID__ +#include +#endif // _WIN32 + +namespace Mui::Ctrl +{ + +#ifndef UIEDITATTRIB +#define UIEDITATTRIB struct Attribute { \ + UIStylePtr style; \ + UILabel::Attribute fontStyle; \ + bool multiline = false; \ + bool password = false; \ + wchar_t passChar = L'*'; \ + bool readOnly = false; \ + bool isRich = false; \ + bool wordWrap = false; \ + bool wordAutoSel = false; \ + bool number = false; \ + bool scroll = false; \ + bool autoBar = true; \ + \ + int limitText = 0x7fffffff; \ + _m_word editAlign = 0; \ + _m_color caretColor = 0xff000000;\ + \ + UIString pholderText; \ + _m_color pholderTextColor = 0xFF606060;\ + } +#endif + + /* UIEditBox 控件 + * 属性列表: + * style (UIStyle*) + * fontStyle (UILabel::Attribute*) - 字体样式 + * text (std::wstring_view) - 当前文本 + * multiline (bool) - 是否为多行编辑框 + * password (bool) - 是否为密码输入模式 + * passChar (wchar_t) - 密码遮掩符 + * readOnly (bool) - 是否为只读模式 + * isRich (bool) - 是否为富文本模式 + * wordWrap (bool) - 是否自动折行 + * wordAutoSel (bool) - 是否自动选择文本 + * number (bool) - 是否为数字输入模式 + * scroll (bool) - 是否有滚动条 + * autoBar (bool) - 自动显示滚动条 + * limitText (int) - 最大可输入文本数 + * editAlign (_m_word) - 编辑框布局样式 ES_XXX + * caretColor (_m_color) - 指针颜色 + * pholderText (std::wstring_view) - 占位符文本 + * pholderTextColor (_m_color) - 占位符文本颜色 + * ... + * 还继承UIScroll的属性... + */ +#ifdef _WIN32 + class UIEditBox : public UIScroll + { + public: + MCTRL_DEFINE_EX + ( + L"UIEditBox", + MCTRL_MAKE_ATTRIB + ( + { CtrlMgr::AttribType::UIStyle, L"style" }, + { CtrlMgr::AttribType::labelStyle, L"fontStyle" } + ), + MCTRL_BASE_ATTRIB(UIScroll) + ); + + MCTRL_DEFSLOT + ( + CtrlMgr::EventSlot textChanged;//文本已被更改 + ); + + UIEDITATTRIB; + + UIEditBox(UIControl* parent, Attribute attrib, UIScroll::Attribute scrollAttrib = {}); + ~UIEditBox() override; + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + template + bool SetAttributeSrc(std::wstring_view attribName, T&& value, bool draw = true) + { + if (!m_attrib.SetAttribute(attribName, std::forward(value), this)) + return UIScroll::SetAttributeSrc(attribName, std::forward(value), draw); + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + return true; + } + [[nodiscard]] const Attribute& GetAttribute() const; + + //设置选中 + void SetSel(int first, int second, bool noScroll = false); + + //替换当前所选内容 + void ReplaceSel(std::wstring_view text, bool canUndo = true); + + //是否自动折行 + bool GetWordWrap() const; + + //设置是否自动折行 + void SetWordWrap(bool wrap); + + //是否只读 + bool GetReadOnly() const; + + //设置只读 + void SetReadOnly(bool read); + + //获取最大文本长度限制 + int GetLimitText() const; + + //设置最大文本长度限制 + void SetLimitText(int lenght); + + //追加文本 + void AppendText(std::wstring_view text, bool canUndo = true); + + //获取对齐方式 + _m_word GetEditAlign() const; + + //设置对齐方式 + void SetEditAlign(_m_word align); + + //是否为富文本模式 + bool IsRichMode() const; + + //设置是否支持富文本 + void SetRichMode(bool rich); + + //设置是否支持多行 + void SetMultiline(bool multiline); + + //设置数字输入模式 + void SetNumber(bool number); + + //设置默认字体颜色 + void SetTextColor(_m_color color); + + //设置当前编辑框内容 + void SetCurText(std::wstring_view text); + + //获取当前内容 + std::wstring GetCurText() const; + + //获取当前选中内容 + std::wstring GetCurSelText() const; + + //获取当前内容长度 GETTEXTLENGTHEX flag + int GetCurTextLength(_m_ulong flag) const; + + //全选内容 + void SelAllText(); + + //设置当前字体样式 + void SetFontStyle(const UILabel::Attribute& fontStyle); + + //添加文本 带颜色 需要富文本支持 + void AddColorText(std::wstring_view text, _m_color color); + + //设置密码输入模式 + void SetPassword(bool password); + + //设置密码遮掩符 + void SetPasswordChar(wchar_t ch = '*'); + + //重做 + bool Redo(); + + //撤销 + bool Undo(); + + //清空 + void Clear(); + + //复制 + void Copy() const; + + //剪切 + void Cut(); + + //粘贴 + void Paste(); + + void SetEnabled(bool enable, bool draw = true) override; + + protected: + explicit UIEditBox(Attribute attrib, UIScroll::Attribute scrollAttrib = {}); + + void OnLoadResource(MRenderCmd* render, bool recreate) override; + + //绘制 + void OnPaintProc(MPCPaintParam param) override; + + //消息处理 + bool OnWindowMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam) override; + bool OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) override; + bool OnSetCursor(_m_param hCur, _m_param lParam) override; + + void OnTimer(_m_ptrv idTimer) override; + + void OnScale(_m_scale scale) override; + + void OnTextChanged(std::wstring str); + + //光标相关 + void CreateCaret(int cx, int cy); + void ShowCaret(bool show); + void SetCaretPos(int x, int y); + void SetCaretColor(_m_color color, MRenderCmd* render); + _m_rect GetCaretRect(); + + //滚动条 + void OnScrollView(UIScroll*, int dragValue, bool horizontal); + + HRESULT TxSendMessage(UINT msg, WPARAM wparam, LPARAM lparam, LRESULT* plresult) const; + void TxServices(std::function task); + + class MTextHost : public ITextHost2 + { + friend class UIEditBox; + public: + virtual ~MTextHost() = default; + ITextServices2* GetServices(); + + void SetClientRect(UIRect* prc); + void SetWordWrap(bool warp); + void SetReadOnly(bool read); + void SetFont(HFONT hFont); + void SetColor(COLORREF color); + void LimitText(); + WORD GetAlign(); + void SetAlign(WORD align); + void SetRichTextFlag(bool rich); + void SetPasswordChar(WCHAR chars); + void SetMultiline(bool multiline); + void SetBarWidth(_m_ushort width); + bool IsITextHost2() { return !IsOldVer; } + + //IUnknown + HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject) override; + ULONG __stdcall AddRef() override; + ULONG __stdcall Release() override; + + //ITextHost + HDC TxGetDC() override { return m_editBox->m_oldPaint.PaintDC; } + INT TxReleaseDC(HDC hdc) override { return TRUE; } + BOOL TxShowScrollBar(INT fnBar, BOOL fShow) override; + BOOL TxEnableScrollBar(INT fuSBFlags, INT fuArrowflags) override { return TRUE; } + BOOL TxSetScrollRange(INT fnBar, LONG nMinPos, INT nMaxPos, BOOL fRedraw) override; + BOOL TxSetScrollPos(INT fnBar, INT nPos, BOOL fRedraw) override; + void TxInvalidateRect(LPCRECT prc, BOOL fMode) override; + void TxViewChange(BOOL fUpdate) override; + BOOL TxCreateCaret(HBITMAP hbmp, INT xWidth, INT yHeight) override; + BOOL TxShowCaret(BOOL fShow) override { return TRUE; } + BOOL TxSetCaretPos(INT x, INT y) override; + BOOL TxSetTimer(UINT idTimer, UINT uTimeout) override { return TRUE; } + void TxKillTimer(UINT idTimer) override { } + void TxScrollWindowEx(INT dx, INT dy, LPCRECT lprcScroll, LPCRECT lprcClip, HRGN hrgnUpdate, LPRECT lprcUpdate, UINT fuScroll) override {} + void TxSetCapture(BOOL fCapture) override; + void TxSetFocus() override; + void TxSetCursor(HCURSOR hcur, BOOL fText) override; + BOOL TxScreenToClient(LPPOINT lppt) override; + BOOL TxClientToScreen(LPPOINT lppt) override; + HRESULT TxActivate(LONG* plOldState) override { return S_OK; } + HRESULT TxDeactivate(LONG lNewState) override { return S_OK; } + HRESULT TxGetClientRect(LPRECT prc) override; + HRESULT TxGetViewInset(LPRECT prc) override; + HRESULT TxGetCharFormat(const CHARFORMATW** ppCF) override; + HRESULT TxGetParaFormat(const PARAFORMAT** ppPF) override; + COLORREF TxGetSysColor(int nIndex) override; + HRESULT TxGetBackStyle(TXTBACKSTYLE* pstyle) override; + HRESULT TxGetMaxLength(DWORD* plength) override; + HRESULT TxGetScrollBars(DWORD* pdwScrollBar) override; + HRESULT TxGetPasswordChar(TCHAR* pch) override; + HRESULT TxGetAcceleratorPos(LONG* pcp) override { return S_OK; } + HRESULT TxGetExtent(LPSIZEL lpExtent) override; + + HRESULT OnTxCharFormatChange(const CHARFORMATW* pcf) override { return S_OK; } + HRESULT OnTxParaFormatChange(const PARAFORMAT* ppf) override { return S_OK; } + HRESULT TxGetPropertyBits(DWORD dwMask, DWORD* pdwBits) override; + HRESULT TxNotify(DWORD iNotify, void* pv) override { return S_OK; } + HIMC TxImmGetContext() override { return nullptr; } + void TxImmReleaseContext(HIMC himc) override { } + HRESULT TxGetSelectionBarWidth(LONG* lSelBarWidth) override; + + //ITextHost2 + HRESULT TxDestroyCaret() override { return S_OK; } + void TxFreeTextServicesNotification() override { } + HRESULT TxGetEastAsianFlags(LONG* pFlags) override { return S_OK; } + HRESULT TxGetEditStyle(DWORD dwItem, DWORD* pdwData) override { return S_OK; } + HRESULT TxGetHorzExtent(LONG* plHorzExtent) override { return S_OK; } + HPALETTE TxGetPalette() override { return nullptr; } + HRESULT TxGetWindow(HWND* phwnd) override { return S_OK; } + HRESULT TxGetWindowStyles(DWORD* pdwStyle, DWORD* pdwExStyle) override { return S_OK; } + BOOL TxIsDoubleClickPending() override { return FALSE; } + HCURSOR TxSetCursor2(HCURSOR hcur, BOOL bText) override { return nullptr; } + HRESULT TxSetForegroundWindow() override { return S_OK; } + HRESULT TxShowDropCaret(BOOL fShow, HDC hdc, LPCRECT prc) override { return S_OK; } + + BOOL Init(UIEditBox* m_pEdit); + HRESULT InitDefaultCharFormat(CHARFORMAT2W* pcf, HFONT hFont); + + HMODULE m_hDll = nullptr; + LONG m_dwRef = 0; + + ITextServices2* m_services = nullptr; + SIZEL m_sizelExtent = { 0,0 }; + CHARFORMAT2W m_charFormat = {}; + PARAFORMAT2 m_paraFormat = {}; + + UIEditBox* m_editBox = nullptr; + + bool IsOldVer = false; + }; + + MTextHost* m_txtHost = nullptr; + + void TextHost(std::function task) const; + + friend class MTextHost; + + UIRect GetInsetFrame(UIRect src); + + struct OldPaintData + { + UISize bufferSize; + HBITMAP PaintBMP; + HDC PaintDC; + } m_oldPaint; + void GDIPaint(MRenderCmd* render, MPCRect destRect, MPCRect insetRect, bool cacheCanvas); + + //ITextHost服务 + void OnInitTextSer(); + + std::wstring GetCurTextInternal() const; + + //字体 + HFONT m_font = nullptr; + HFONT GetFont(); + + HBRUSH m_ClearBrush = nullptr; + + mutable CtrlMgr::UIAttribute m_attrib; + + static void BindAttribute(); + + //光标 + UISize CaretSize; + UIPoint CaretPos; + _m_rect CaretRect; + _m_color CaretColor = 0xff000000; + MBrushPtr CaretBrush = nullptr; + bool CaretVisiable = false; + MTimers::ID CaretTimer = 0; + + UIControlStatus m_controlState = UIControlStatus_Normal; + + //鼠标是否在滚动条区域 + bool m_mouseInScroll = false; + bool m_mouseDown = false; + + bool m_autoWordSel = false; + }; +#endif +#ifdef __ANDROID__ + class UIEditBox : public UILabel + { + public: + UIEditBox(); + UIEditBox(UIControl* parent, UIEditBoxConfig config); + virtual ~UIEditBox(); + + M_DEF_CTRL(L"UIEditBox"); + + void SetProperty(UIEditBoxConfig config, bool draw = true); + UIEditBoxConfig GetProperty(); + + virtual void SetAttribute(std::wstring attribName, std::wstring attrib, bool draw = true) override; + virtual std::wstring GetAttribute(std::wstring attribName) override; + + //设置选中 + void SetSel(_m_ulong dwSelection, bool bNoScroll = false) { } + + //替换当前所选内容 + void ReplaceSel(std::wstring text, bool bCanUndo = true) { } + + //是否自动折行 + bool GetWordWrap() { return m_config.WordWrap; } + + //设置是否自动折行 + void SetWordWrap(bool fWordWrap) { m_config.WordWrap = fWordWrap; } + + //是否只读 + bool GetReadOnly() { return m_config.ReadOnly; } + + //设置只读 + bool SetReadOnly(bool bReadOnly) { return m_config.ReadOnly = bReadOnly; } + + //获取最大文本长度 + _m_long GetLimitText() { return m_config.LimitText; } + + //设置最大文本长度 + bool SetLimitText(_m_long nLength) { m_config.LimitText = nLength; } + + //追加文本 + void AppendText(std::wstring text, bool bCanUndo = true); + + //获取对齐方式 + _m_word GetDefaultAlign() { return m_align; } + + //设置对齐方式 + void SetDefaultAlign(_m_word wNewAlign); + + //是否为富文本模式 + bool GetRichTextFlag() { return false; } + + //设置是否支持富文本 + void SetRichTextFlag(bool fRich) { m_config.IsRich = fRich; } + + //设置默认字体颜色 + _m_color SetDefaultTextColor(_m_color cr); + + //设置当前编辑框内容 + void SetCurText(std::wstring text); + + //获取当前内容 + std::wstring GetCurText() { return m_config.m_textConfig.Text; } + + //获取当前选中内容 + std::wstring GetCurSelText() { return L""; } + + //获取当前内容长度 + _m_long GetCurTextLength(_m_ulong flag) { return m_config.m_textConfig.Text.length(); } + + //全选内容 + void SelAllText() {} + + //设置是否支持多行 + void SetMultiline(bool multiline); + + //设置数字输入模式 + void SetNumber(bool number); + + //设置当前字体 + void SetFontStyle(UILabel::Property fontStyle); + + //添加文本 带颜色 + void AddColorText(std::wstring text, _m_color color); + + //取滚动条滚动范围 + int GetScrollShift(bool hBar = false) { return 0; }; + + //取滚动条滑块位置 + int GetScrollDragPos(bool hBar = false) { return 0; }; + + //设置滚动条滑块位置 + void SetScrollDragPos(int pos, bool hBar = false) { }; + + //设置密码输入模式 + void SetPassword(bool password); + + //恢复 + bool Redo() { return true; } + + //撤销 + bool Undo() { return true; } + + //清空 + void Clear() { SetCurText(L""); } + + //复制 + void Copy() {} + + //剪切 + void Cut() {} + + //粘贴 + void Paste() {} + + //绘制 + virtual void OnPaintProc(MRenderCmd* render, MPCRect clipRect, MPCRect destRect, bool cacheCanvas) override; + + protected: + virtual bool OnMouseEntered(_m_uint flag, const UIPoint& point) override; + virtual bool OnMouseExited(_m_uint flag, const UIPoint& point) override; + virtual bool OnLButtonDown(_m_uint flag, const UIPoint& point) override; + virtual bool OnLButtonUp(_m_uint flag, const UIPoint& point) override; + + UIEditBoxConfig m_config; + std::wstring m_passStr; + _m_word m_align = 0; + UIControlStatus m_status = UIControlStatus_Normal; + }; +#endif +} diff --git a/MiaoUI/src/include/Control/Mui_ImgBox.h b/MiaoUI/src/include/Control/Mui_ImgBox.h new file mode 100644 index 0000000..7f424b1 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_ImgBox.h @@ -0,0 +1,79 @@ +/** + * FileName: Mui_ImgBox.h + * Note: UI图片框控件声明 + * + * Copyright (C) 2020-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-23 Create +*/ + +#pragma once +#include + +namespace Mui::Ctrl +{ + /* UIButton 控件 + * 属性列表: + * img (UIBitmap*) - 图片 + * imgStyle (ImgStyle) - 图片样式 + */ + class UIImgBox : public UIControl + { + public: + MCTRL_DEFINE + ( + L"UIImgBox", + { CtrlMgr::AttribType::UIBitmap, L"img" } + ); + + UIImgBox(UIControl* parent); + + //图片样式 + enum ImgStyle + { + ImageBoxStyle_Zoom, //缩放 + ImageBoxStyle_Original, //原始尺寸 + ImageBoxStyle_Center, //居中 + ImageBoxStyle_ZoomFill //缩放并填充 + }; + + //取图像尺寸 + UISize GetImageSize() const; + + //设置图像 + void SetImage(UIBitmapPtr img, bool lowQuality = false, bool draw = true); + + //设置图像呈现样式 + void SetImageStyle(ImgStyle style, bool draw = true); + + //设置图像质量 + void SetQuality(bool lowQuality, bool draw = true); + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + protected: + UIImgBox() { m_cacheSupport = true; } + + void OnPaintProc(MPCPaintParam param) override; + + _m_sizef GetContentSize() override; + + ImgStyle m_style = ImageBoxStyle_Zoom; + UIBitmapPtr m_image = nullptr; + bool m_isLowQuality = false; + }; + +} diff --git a/MiaoUI/src/include/Control/Mui_Label.h b/MiaoUI/src/include/Control/Mui_Label.h new file mode 100644 index 0000000..329dc61 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_Label.h @@ -0,0 +1,134 @@ +/** + * FileName: Mui_Label.h + * Note: UI标签控件声明 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-23 Create +*/ + +#pragma once +#include + +namespace Mui::Ctrl +{ + /* UI标签控件 + * 属性列表: + * fontStyle + * text (std::wstring_view) - 文本 + * font (std::wstring_view) - 字体名称 + * fontSize (_m_ushort) - 字体尺寸 默认12px + * fontBold (bool) - 是否粗体 + * fontItalics (bool) - 是否斜体 + * fontUnderline (bool) - 是否下划线 + * fontStrikeout (bool) - 是否删除线 + * fontColor 4x - 字体颜色 默认RGBA(0,0,0,255) + * hyperlink (bool) - 是否为超链接 + * url (std::wstring_view) - 超链接网址 + * urlColor 4x - 超链接热点颜色 默认RGBA(167,226,54,255) + * textAlign - 文本对齐方式 仅数字 为TextAlignment枚举值 默认LeftTop + * fontCustom (_m_ptrv) - 自定义字体集对象参数 Lite版本无效 + * shadowUse (bool) - 使用文本阴影效果 + * shadowBlur (float) - 文本阴影模糊度 默认1.f + * shadowColor (_m_color) 4x - 文本阴影颜色 默认RGBA(0,0,0,255) + * shadowOffset (UIPoint) 2x - 文本阴影偏移位置 x,y 默认 1,1 + * shadowLow (bool) - 是否使用低质量阴影 如果为true=没有模糊效果 blur属性无效 + */ + class UILabel : public UIControl + { + public: + MCTRL_DEFINE + ( + L"UILabel", + { CtrlMgr::AttribType::labelStyle, L"fontStyle" } + ); + MCTRL_DEFSLOT + ( + CtrlMgr::EventSlot clicked;//鼠标左键单击 + ); + + struct Attribute + { + UIString text; + UIString font = M_DEF_SYSTEM_FONTNAME; + UIString url; + + _m_color fontColor = Color::M_Black; + _m_color urlColor = 0xffa7e236; + _m_color shadowColor = Color::M_Black; + + _m_ushort fontSize = 12; + _m_ptrv fontCustom = 0; + + bool hyperlink = false; + bool shadowUse = false; + bool shadowLow = false; + + float shadowBlur = 1.f; + + UIPoint shadowOffset = { 1,1 }; + UIFontStyle fontStyle; + TextAlign textAlign = TextAlign_Top; + }; + + UILabel(UIControl* parent, Attribute attrib); + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + template + bool SetAttributeSrc(std::wstring_view attribName, T&& value, bool draw = true) + { + return m_attrib.SetAttribute(attribName, std::forward(value), { this, draw }); + } + [[nodiscard]] const Attribute& GetAttribute() const { return m_attrib.Get(); } + + //取文本尺寸 + [[nodiscard]] UISize GetTextMetric(bool dpi = true) const; + + protected: + explicit UILabel(Attribute attrib); + + UISize CalcSize(); + + void OnLoadResource(MRenderCmd* render, bool recreate) override; + + void OnPaintProc(MPCPaintParam param) override; + + bool OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) override; + + bool OnSetCursor(_m_param hCur, _m_param lParam) override; + + void OnScale(_m_scale scale) override; + + _m_sizef GetContentSize() override; + + //偏移绘制 + bool OffsetDraw = false; + _m_rect OffsetDrawRc = { 0,0,0,0 }; + + MFontPtr m_font = nullptr; + MBrushPtr m_brush = nullptr; + MEffectPtr m_effect = nullptr; + + bool m_mouseIn = false; + bool m_isClick = false; + + CtrlMgr::UIAttribute> m_attrib; + static void BindAttribute(); + void updateStyle(UIFontStyle& style); + bool updateRender(bool draw, bool layout = false); + }; +} diff --git a/MiaoUI/src/include/Control/Mui_ListBox.h b/MiaoUI/src/include/Control/Mui_ListBox.h new file mode 100644 index 0000000..a5e82a5 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_ListBox.h @@ -0,0 +1,303 @@ +/** + * FileName: Mui_ListBox.h + * Note: UI列表框控件声明 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-11-28 Create +*/ +#pragma once +#include + +namespace Mui::Ctrl +{ + class UIListBox; + + class ListItem + { + public: + ListItem() = default; + ListItem(std::wstring_view title, _m_color color = 0, UIFontStyle style = {}); + + virtual ~ListItem(); + + virtual void SetText(std::wstring_view text, _m_color color = 0, UIFontStyle style = {}, bool updateStyle = false); + + virtual void SetFontSize(_m_uint size); + + virtual std::wstring_view GetText(); + + protected: + enum ItemStatus + { + UIListItemNormal, //普通 + UIListItemHover, //热点 + UIListItemPressed, //按下 + UIListItemDisbale, //禁止 + UIListItemNormalSel, //普通选中 + UIListItemHoverSel, //热点选中 + UIListItemPressedSel, //按下选中 + UIListItemDisbaleSel //禁止选中 + }; + + struct PaintParam + { + MRenderCmd* render = nullptr; + MPCRect dstRect = nullptr; + UIStyle* style = nullptr; + _m_byte dstAlpha = 0; + _m_rcscale dstScale; + UIPoint offset; + _m_color defcolor = 0; + }; + + virtual void OnPaintProc(const PaintParam* param); + + virtual void PaintStyle(MRenderCmd* render, UIStyle* style, _m_rect frame, + ItemStatus state, _m_byte alpha, _m_scale scale = { 1.f, 1.f }); + + virtual bool OnMouseMessage(_m_uint message, _m_param wParam, _m_param lParam); + + bool IsSel(); + + UIListBox* m_parent = nullptr; + ItemStatus m_state = UIListItemNormal; + + bool m_isClick = false; + + UIString m_text; + UIFontStyle m_style; + _m_uint m_size = 12; + _m_color m_color = 0; + + friend class UIListBox; + }; + + /* UIListBox 控件 + * 属性列表: + * style (UIStyle*) + * itemStyle (UIStyle*) - 列表项样式 + * itemHeight (int) - 列表项高度(px) + * lineSpace (int) - 列表行间距(px) + * iFontStyle (UIListBox::ItemFont*) - 列表项字体默认样式 + * iFont (std::wstring_view) - 列表项字体默认样式 - 字体名称 + * iFontSize (_m_ushort) - 列表项字体默认样式 - 字体尺寸 默认12px + * iFontBold (bool) - 列表项字体默认样式 - 是否粗体 + * iFontItalics (bool) - 列表项字体默认样式 - 是否斜体 + * iFontUnderline (bool) - 列表项字体默认样式 - 是否下划线 + * iFontStrikeout (bool) - 列表项字体默认样式 - 是否删除线 + * iFontColor 4x - 列表项字体默认样式 - 字体颜色 默认RGBA(0,0,0,255) + * iTextAlign - 列表项字体默认样式 - 文本对齐方式 仅数字 为TextAlign枚举值 默认LeftTop + * iFontCustom (_m_param) - 列表项字体默认样式 - 自定义字体集对象参数 + * drawOffset (UIPoint) 2x - 列表内容绘制偏移参数(px) + * allowRightSel (bool) - 允许右键点击也能选中列表项目 + * ... + * 还继承UIScroll的属性... + */ + class UIListBox : public UIScroll + { + public: + MCTRL_DEFINE_EX + ( + L"UIListBox", + MCTRL_MAKE_ATTRIB + ( + { CtrlMgr::AttribType::UIStyle, L"style" }, + { CtrlMgr::AttribType::UIStyle, L"itemStyle" }, + { CtrlMgr::AttribType::listfontStyle, L"iFontStyle" } + ), + MCTRL_BASE_ATTRIB(UIScroll) + ); + + MCTRL_DEFSLOT + ( + CtrlMgr::EventSlot itemClicked;//项目被左键单击 + CtrlMgr::EventSlot itemDBClicked;//项目被左键双击 + CtrlMgr::EventSlot itemChanging;//选中项目即将被更改 设置变量为false则阻止更改 + CtrlMgr::EventSlot itemChanged;//选中项目已被更改 + ); + + struct ItemFont + { + UIString font = M_DEF_SYSTEM_FONTNAME; + + _m_color fontColor = 0xff000000; + + _m_ushort fontSize = 12; + _m_ptrv fontCustom = 0; + + UIFontStyle fontStyle; + TextAlign textAlign = TextAlign_Top; + }; + + struct Attribute + { + UIStylePtr style = nullptr; + UIStylePtr itemStyle = nullptr; + + int itemHeight = 30; + int lineSpace = 0; + bool allowRightSel = false; + + UIPoint drawOffset; + + ItemFont fontStyle; + }; + + UIListBox(UIControl* parent, Attribute attrib, UIScroll::Attribute scrollAttrib = {}); + ~UIListBox() override; + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + template + bool SetAttributeSrc(std::wstring_view attribName, T&& value, bool draw = true) + { + if (!m_attrib.SetAttribute(attribName, std::forward(value), this)) + return UIScroll::SetAttributeSrc(attribName, std::forward(value), draw); + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + return true; + } + [[nodiscard]] const Attribute& GetAttribute() const; + + /*添加一个列表项目 + * @param item - 自定义列表项目 + * @param index - 列表位置 -1=尾部 + * @param draw - 是否立即绘制 + * + * @return 返回item总数 + * @exception MErrorCode::IndexOutOfRange + */ + int AddItem(ListItem* item, int index, bool draw = true); + + /*获取一个列表项目指针 + * @param index - 列表索引 -1=尾部 + * + * @return 成功返回列表指针 失败返回nullptr + */ + [[nodiscard]] ListItem* GetItem(int index) const; + + /*设置当前选中的列表项目 + * @param index - 索引项目 -1=不选中 + * @param draw - 是否立即绘制 + * + * @return 是否成功 若失败则索引值无效 + */ + bool SetCurSelItem(int index, bool draw = true); + + //获取当前选中的列表项目索引 -1=未选中 + [[nodiscard]] int GetCurSelItem() const; + + //获取列表项目总数 + [[nodiscard]] int GetItemListCount() const; + + /*删除一个列表项目 这不会删除ListItem的内存 + * @param index - 列表索引 -1=尾部 + * @param draw - 是否立即绘制 + * + * @return 当前列表项目总数 + * @exception MErrorCode::IndexOutOfRange + */ + int DeleteItem(int index, bool draw = true); + + /*删除一个列表项目 + * @param index - 列表索引 -1=尾部 + * @param delItem - 是否删除列表内存 使用delete + * @param draw - 是否立即绘制 + * + * @return 当前列表项目总数 + * @exception MErrorCode::IndexOutOfRange + */ + int DeleteItem(int index, bool delItem, bool draw = true); + + /*删除所有列表项 + * @param delItem - 是否删除列表内存 使用delete + * @param draw - 是否立即绘制 + */ + void DeleteAllItem(bool delItem = true, bool draw = true); + + protected: + explicit UIListBox(Attribute attrib, UIScroll::Attribute scrollAttrib = {}); + + void OnLoadResource(MRenderCmd* render, bool recreate) override; + + void OnPaintProc(MPCPaintParam param) override; + + bool OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) override; + + void OnScrollView(UIScroll*, int dragValue, bool horizontal); + + bool DispatchItemMsg(UIPoint& point, _m_uint msg, _m_param wParam, _m_param lParam); + + void OnLayoutCalced() override { CalcListView(); } + + //设置视图Top + void SetViewTop(int top); + //索引检查 防止越界 + bool IndexCheck(int index) const; + //计算当前可视区域 + void CalcListView(); + //绘制视图列表 + void PaintListView(MRenderCmd* render, MPCRect destRect, bool cache); + //设置当前选中项目 + void SetSelItem(ListItem* item, UIPoint pt); + //更新索引 + void UpdateIndex(); + + //默认字体和画刷 + MFontPtr m_fontDef = nullptr; + MBrushPtr m_brushDef = nullptr; + + private: + CtrlMgr::UIAttribute m_attrib; + + static void BindAttribute(); + + void updateStyle(UIFontStyle& style); + + protected: + UIControlStatus m_state = UIControlStatus_Normal; + + //项目列表 + std::vector m_itemList; + + struct ViewData + { + UIPoint offsetPos; + int beginIndex = 0; + int indexCount = 0; + + int top = 0; + int bottom = 0; + int viewHeight = 0; + int itemHeight = 0; + int lineSpace = 0; + UIRect boxRect; + _m_rcscale scale; + std::vector viewItemRect; + } m_view; + + int m_curSelIndex = -1;//当前选中项目索引 + ListItem* m_hoverIndex = nullptr;//当前焦点项目 + ListItem* m_curSelItem = nullptr;//当前选中项目 + UIRect m_curItemRect; + + //模拟触控滑动相关 + int m_downViewY = 0; + friend class ListItem; + }; + +} diff --git a/MiaoUI/src/include/Control/Mui_NavBar.h b/MiaoUI/src/include/Control/Mui_NavBar.h new file mode 100644 index 0000000..869901d --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_NavBar.h @@ -0,0 +1,191 @@ +/** + * FileName: Mui_NavBar.h + * Note: UI导航栏声明 + * + * Copyright (C) 2022-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-9-7 Create +*/ +#pragma once +#include + +namespace Mui::Ctrl +{ + /* UINavBar(UINavigationBar) 控件 MiaoUI Core 2.0.6版本 + * 属性列表: + * fontStyle (UILabel::Attribute*) - 字体样式 + * font (std::wstring_view) - 字体 + * fontColor (_m_color) 4x - 字体颜色 默认RGBA(120,120,120,255) + * fontHoverColor (_m_color) 4x - 字体颜色 Hover状态 默认RGBA(0,0,0,255) + * fontPressColor (_m_color) 4x - 字体颜色 按下状态 默认RGBA(150,150,150,255) + * fontSize (_m_ushort) - 字体尺寸 默认12px + * fontBold (bool) - 是否粗体 + * fontItalics (bool) - 是否斜体 + * fontUnderline (bool) - 是否下划线 + * fontStrikeout (bool) - 是否删除线 + * fontCustom (_m_ptrv) - 自定义字体集对象参数 + * shadowUse (bool) - 使用文本阴影效果 + * shadowBlur (float) - 文本阴影模糊度 默认1.f + * shadowColor (_m_color) 4x - 文本阴影颜色 默认RGBA(0,0,0,255) + * shadowOffset (UIPoint) 2x - 文本阴影偏移位置 x,y 默认 1,1 + * shadowLow (bool) - 是否使用低质量阴影 如果为true=没有模糊效果 blur属性无效 + * itemSpace (int) - item间距 + * barSpace (int) - 横条和文字间距 + * barHeight (int) - 横条高度 默认4px + * barAnitime (int) - 横条过渡动画时长(ms) 默认300ms + * barRound (float) - 横条圆角度 默认1.5f + * barColor (_m_color) 4x - 横条颜色 + */ + class UINavBar : public UIControl + { + public: + MCTRL_DEFINE + ( + L"UINavBar", + { CtrlMgr::AttribType::labelStyle, L"fontStyle" } + ); + + struct Attribute + { + UIString font = M_DEF_SYSTEM_FONTNAME; + _m_color fontColor = 0xff787878; + _m_color fontHoverColor = Color::M_Black; + _m_color fontPressColor = 0xff969696; + _m_ushort fontSize = 12; + UIFontStyle fontStyle; + _m_ptrv fontCustom = 0; + + bool shadowUse = false; + bool shadowLow = false; + float shadowBlur = 1.f; + UIPoint shadowOffset = { 1,1 }; + _m_color shadowColor = Color::M_Black; + + int itemSpace = 10; + int barSpace = 5; + int barHeight = 4; + int barAnitime = 300; + float barRound = 1.5f; + _m_color barColor = 0xffffb375; + }; + + UINavBar(UIControl* parent, Attribute attrib); + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + template + bool SetAttributeSrc(std::wstring_view attribName, T&& value, bool draw = true) + { + if (m_attrib.SetAttribute(attribName, std::forward(value), this)) + { + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + return true; + } + return false; + } + [[nodiscard]] const Attribute& GetAttribute() const { return m_attrib.Get(); } + + void SetFontStyle(UILabel::Attribute& attrib, bool draw = true); + + /*添加一个标题项目 + * @param title - 标题 + * @param index - 列表位置 -1=尾部 + * @param draw - 是否立即绘制 + * + * @return 当前项目总数 + */ + int AddItem(std::wstring_view title, int index = -1, bool draw = true); + + //设置项目标题 -1=尾部 + void SetItemTitle(int index, std::wstring_view title, bool draw = true); + + //获取项目标题 -1=尾部 + [[nodiscard]] std::wstring GetItemTitle(int index) const; + + /*设置当前选中的列表项目 + * @param index - 索引项目 + * @param draw - 是否立即绘制 + * + * @return 是否成功 若失败则索引值无效 + */ + bool SetCurSelItem(int index, bool draw = true); + + //获取当前选中的列表项目索引 + [[nodiscard]] int GetCurSelItem() const; + + //获取列表项目总数 + [[nodiscard]] int GetItemListCount() const; + + /*删除一个列表项目 + * @param index - 列表索引 -1=尾部 + * @param draw - 是否立即绘制 + * + * @return 当前项目总数 + */ + int DeleteItem(int index, bool draw = true); + + //删除所有列表项 + void DeleteAllItem(bool draw = true); + + protected: + explicit UINavBar(Attribute attrib); + + void OnLoadResource(MRenderCmd* render, bool recreate) override; + void OnPaintProc(MPCPaintParam param) override; + bool OnMouseEntered(_m_uint flag, const UIPoint& point) override; + bool OnMouseExited(_m_uint flag, const UIPoint& point) override; + bool OnLButtonDown(_m_uint flag, const UIPoint& point) override; + bool OnLButtonUp(_m_uint flag, const UIPoint& point) override; + bool OnMouseMove(_m_uint flag, const UIPoint& point) override; + + void OnScale(_m_scale scale) override; + _m_sizef GetContentSize() override; + void OnLayoutCalced() override; + + bool IndexCheck(int index) const; + + private: + std::pair InitItemRect(std::wstring_view title, int index); + void CalcItemRect(); + bool IsHitItem(const UIPoint& pt); + + CtrlMgr::UIAttribute> m_attrib; + static void BindAttribute(); + + void updateStyle(UIFontStyle& style); + bool updateRender(bool draw, bool layout = false); + + MFontPtr m_font = nullptr; + MBrushPtr m_brush = nullptr; + MEffectPtr m_effect = nullptr; + + UISize m_size; + + std::vector> m_itemList; + + int m_curSelIndex = 0; + int m_curHoverIndex = -1; + UIControlStatus m_state = UIControlStatus_Normal; + + bool m_down = false; + int m_lastHover = -1; + + protected: + int m_baroffset = 0; + int m_barwidth = 0; + }; +} \ No newline at end of file diff --git a/MiaoUI/src/include/Control/Mui_Progress.h b/MiaoUI/src/include/Control/Mui_Progress.h new file mode 100644 index 0000000..88e91d2 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_Progress.h @@ -0,0 +1,96 @@ +/** + * FileName: Mui_Progress.h + * Note: UI进度条控件声明 + * + * Copyright (C) 2020-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-11-24 Create +*/ + +#pragma once +#include + +namespace Mui::Ctrl +{ + /* UIProgBar(UIProgressBar) 控件 + * 属性列表: + * style (UIStyle*) + * maxValue (int) - 最大值 + * value (int) - 当前值 + * vertical (bool) - 是否垂直 + * leftShow (bool) - 如果非垂直 进度从左往右显示 (默认true) + * bottomShow (bool) - 如果非水平 进度从下往上显示 (默认true) + */ + class UIProgressBar : public UIControl + { + public: + MCTRL_DEFINE(L"UIProgBar", { CtrlMgr::AttribType::UIStyle, L"style" }); + + struct Attribute + { + UIStylePtr style = nullptr; + int maxValue = 100; + int value = 0; + bool vertical = false; + bool leftShow = true; + bool bottomShow = true; + }; + + UIProgressBar(UIControl* parent, Attribute attrib); + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + template + bool SetAttributeSrc(std::wstring_view attribName, T&& value, bool draw = true) + { + if (m_attrib.SetAttribute(attribName, std::forward(value), draw)) + { + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + return true; + } + return false; + } + [[nodiscard]] const Attribute& GetAttribute() const { return m_attrib.Get(); } + + //设置最大值 + void SetMaxValue(int value, bool draw = true); + //获取最大值 + int GetMaxValue() const; + + //设置当前值 + void SetCurValue(int value, bool draw = true); + //获取当前值 + int GetCurValue() const; + + protected: + explicit UIProgressBar(Attribute attrib); + + void OnPaintProc(MPCPaintParam param) override; + + //计算百分比值 + int Percentage(int value1, int percentValue, int percent); + //计算块矩形 + void CalcDrawRect(MPCRect dest); + + private: + CtrlMgr::UIAttribute m_attrib; + static void BindAttribute(); + + UIRect m_drawRect; + }; + +} diff --git a/MiaoUI/src/include/Control/Mui_Scroll.h b/MiaoUI/src/include/Control/Mui_Scroll.h new file mode 100644 index 0000000..ea49e27 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_Scroll.h @@ -0,0 +1,216 @@ +/** + * FileName: Mui_Scroll.h + * Note: UI滚动条控件声明 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-11-15 Create +*/ + +#pragma once +#include + +namespace Mui::Ctrl +{ + /* UIScroll 控件 + * 属性列表: + * styleV (UIStyle*) - 垂直滚动条样式 + * styleH (UIStyle*) - 水平滚动条样式 + * vertical (bool) - 显示垂直滚动条 + * horizontal (bool) - 显示水平滚动条 + * active (bool) - 始终显示滚动条(无效) + * animate (bool) - 是否启用动画效果 Lite版本无效 + * button (bool) - 是否显示滚动条上下调节按钮 + * range (UISize) - 滚动条最大可滚动区域 + * rangeV (int) - 垂直滚动条最大可滚动区域 + * rangeH (int) - 水平滚动条最大可滚动区域 + * dragValue (UISize) - 滚动条当前值 + * dragValueV (int) - 垂直滚动条当前值 + * dragValueH (int) - 水平滚动条当前值 + * barWidth (int) - 滚动条宽度 + * barMinHeight (int) - 滚动条按钮最小高(宽)度 + * btnHeight (int) - 滚动条上下调节按钮高度 + * inset (_m_rect_t) - 内边距 + */ + class UIScroll : public UIControl + { + public: + MCTRL_DEFINE + ( + L"UIScroll", + { CtrlMgr::AttribType::UIStyle, L"styleV" }, + { CtrlMgr::AttribType::UIStyle, L"styleH" } + ); + + /* 回调函数 + * @param UIScroll* 滚动条控件指针 + * @param int 滚动条当前值 + * @param bool 是否为水平滚动条 否则为垂直 + */ + using ScrollCallBack = std::function; + + struct Attribute + { + UIStylePtr styleV = nullptr; + UIStylePtr styleH = nullptr; + + ScrollCallBack callback = nullptr; + + bool vertical = false; + bool horizontal = false; + bool active = true; + bool animate = true; //Lite版本无效 + bool button = true; + UISize range; + UISize dragValue; + int barWidth = 8; + int barMinHeight = 20; + int btnHeight = 8; + _m_rect_t inset = { 0 }; + }; + + UIScroll(UIControl* parent, Attribute attrib); + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + template + bool SetAttributeSrc(std::wstring_view attribName, T&& value, bool draw = true) + { + if (m_attrib.SetAttribute(attribName, std::forward(value), this)) + { + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + return true; + } + return false; + } + [[nodiscard]] const Attribute& GetAttribute() const { return m_attrib.Get(); } + + /*获取滚动条可拖动范围 + * @param horizontal - 是否为水平滚动条 + * + * @return 返回指定滚动条值 + */ + [[nodiscard]] int GetRange(bool horizontal) const; + + [[nodiscard]] UISize GetRange() const; + + /*设置可滚动范围 + * @param horizontal - 是否为水平滚动条 + * @param range - 范围 该值不能小于DragValue + * @param draw - 是否立即绘制 + */ + void SetRange(bool horizontal, int range, bool draw = true); + + void SetRange(UISize range, bool draw = true); + + /*获取拖拽按钮当前值 + * @param horizontal - 是否为水平滚动条 + * + * @return 返回指定滚动条值 + */ + [[nodiscard]] int GetDragValue(bool horizontal) const; + + [[nodiscard]] UISize GetDragValue() const; + + /*设置拖拽按钮当前值 + * @param horizontal - 是否为水平滚动条 + * @param value - 滚动值 0-Range + * @param draw - 是否立即绘制 + */ + void SetDragValue(bool horizontal, int value, bool draw = true); + void SetDragValueNoAni(bool horizontal, int value, bool draw); + + /*计算偏移滚动值,值用于控件的底边和顶边转换 + * @param horizontal - 是否计算水平滚动条的值 + * @param dragValue - 要计算的滚动值 + * @param offset - 要计算的值 + * + * @return 去除offset后的dragValue + */ + int CalcOffsetDragValue(bool horizontal, int dragValue, int offset); + + protected: + enum ScrollButtonType + { + ButtonNull, + TopButton, + BottomButton, + ThumbButton, + Track, + Intersect + }; + + explicit UIScroll(Attribute attrib); + + void OnPaintProc(MPCPaintParam param) override; + + bool OnMouseMove(_m_uint flag, const UIPoint& point) override; + bool OnMouseExited(_m_uint flag, const UIPoint& point) override; + bool OnLButtonDown(_m_uint flag, const UIPoint& point) override; + bool OnLButtonUp(_m_uint flag, const UIPoint& point) override; + bool OnMouseWheel(_m_uint flag, short delta, const UIPoint& point) override; + + //计算鼠标在哪个部件内 + ScrollButtonType CalcPointIn(const UIPoint& pt); + + //计算滚动条控件内的各部件矩形 + void CalcControlRect(); + //根据DragValue计算拖拽按钮位置 + void CalcThumbBtnPos(bool horizontal); + //根据ThumbButton位置计算DragValue + void CalcDragValue(bool horizontal); + //绘制部件 + void PaintControl(MRenderCmd* render, UIStyle* style, UIControlStatus status, + ScrollButtonType type, UIRect& drawRect, _m_byte& alpha, _m_scale scale); + + //指定整个控件范围Wheel消息都可以触发滚动条 否则仅限滚动条区域 + bool m_ALLWheel = false; + + void SetCallback(const ScrollCallBack& callback) + { + m_attrib.Set().callback = callback; + } + + private: + + //获得一个滚动page后的pos + int GetScrollPage(bool up, bool horz, float count); + + CtrlMgr::UIAttribute m_attrib; + + static void BindAttribute(); + + UISize m_size; + + struct ScrollData + { + UIRect scrollBtn[2];//滚动调节按钮 + UIRect thumbButton;//拖拽按钮 + UIRect track;//轨道 + }; + + UIRect m_intersect;//垂直滚动条与水平滚动条的交汇点 + ScrollData m_scroll[2]; + + UIPoint m_clickPos; + + UIControlStatus m_status = UIControlStatus_Normal; + ScrollButtonType m_btnType = ButtonNull; + bool m_btnTypeH = false; + bool m_isClick = false; + }; +} diff --git a/MiaoUI/src/include/Control/Mui_Slider.h b/MiaoUI/src/include/Control/Mui_Slider.h new file mode 100644 index 0000000..2cb3194 --- /dev/null +++ b/MiaoUI/src/include/Control/Mui_Slider.h @@ -0,0 +1,141 @@ +/** + * FileName: Mui_Slider.h + * Note: UI滑块条控件声明 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-12-20 Create +*/ + +#pragma once +#include + +namespace Mui::Ctrl +{ + /* UISlider 控件 + * 属性列表: + * style (UIStyle*) + * btnStyle (UIStyle*) - 滑块按钮样式 + * maxValue (int) - 最大值 + * minValue (int) - 最小值 + * value (int) - 当前值 + * vertical (bool) - 是否垂直 + * leftShow (bool) - 如果非垂直 进度从左往右显示 (默认true) + * bottomShow (bool) - 如果非水平 进度从下往上显示 (默认true) + * trackInset (_m_rect_t) 4x - 轨道内边距 + * btnSize (UISize) 2x - 拖拽按钮尺寸 0=自动计算 + */ + class UISlider : public UIControl + { + public: + MCTRL_DEFINE + ( + L"UISlider", + { CtrlMgr::AttribType::UIStyle, L"style" }, + { CtrlMgr::AttribType::UIStyle, L"btnStyle" } + ); + MCTRL_DEFSLOT + ( + CtrlMgr::EventSlot valueChanged;//滑块值已更改 + ); + + struct Attribute + { + UIStylePtr style = nullptr; + UIStylePtr btnStyle = nullptr; + + int maxValue = 100; + int minValue = 0; + int value = 0; + bool vertical = false; + bool leftShow = true; + bool bottomShow = true; + _m_rect_t trackInset = { 0 }; + UISize btnSize; + }; + + UISlider(UIControl* parent, Attribute attrib); + + void SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw = true) override; + std::wstring GetAttribute(std::wstring_view attribName) override; + + template + bool SetAttributeSrc(std::wstring_view attribName, T&& value, bool draw = true) + { + if (m_attrib.SetAttribute(attribName, std::forward(value), draw)) + { + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + return true; + } + return false; + } + [[nodiscard]] const Attribute& GetAttribute() const { return m_attrib.Get(); } + + //设置当前值 + void SetCurValue(int value, bool draw = true); + //获取当前值 + int GetCurValue() const; + //设置最大值 + void SetMaxValue(int max, bool draw = true); + //获取最大值 + int GetMaxValue() const; + //设置最小值 + void SetMinValue(int min, bool draw = true); + //获取最小值 + int GetMinValue() const; + + protected: + enum UISliderStatus + { + UISliderTrackNormal, //轨道背景 普通 + UISliderTrackDisable = 2, //轨道背景 禁止 + UISliderBlockNormal = 1, //块前景 普通 + UISliderBlockDisable = 3, //块前景 禁止 + UISliderBtnNormal = 0, //拖拽按钮 普通 + UISliderBtnHover, //拖拽按钮 热点 + UISliderBtnPressed, //拖拽按钮 按下 + UISliderBtnDisable //拖拽按钮 禁止 + }; + + explicit UISlider(Attribute attrib); + + void OnPaintProc(MPCPaintParam param) override; + + bool OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) override; + + //计算百分比值 + int Percentage(int value1, int percentValue, int percent); + //计算矩形 + void CalcDrawRect(); + //计算值 + int CalcPtValue(UIPoint pt); + + private: + CtrlMgr::UIAttribute m_attrib; + static void BindAttribute(); + + UISliderStatus btnStatus = UISliderBtnNormal; + + UIRect m_drawRect; + UIRect m_drawBtnRect; + UIRect m_drawBgRect; + bool m_isClick = false; + int m_oldValue = 0; + UIPoint m_downpos; + bool m_hitbtn = false; + }; + +} diff --git a/MiaoUI/src/include/FileSystem/DreamMoonRes.h b/MiaoUI/src/include/FileSystem/DreamMoonRes.h new file mode 100644 index 0000000..e7f5994 --- /dev/null +++ b/MiaoUI/src/include/FileSystem/DreamMoonRes.h @@ -0,0 +1,204 @@ +/** + * FileName: DreamMoonRes.h + * Note: 梦月资源类声明 + * + * Copyright (C) 2020-2022 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-12-8 Create +*/ +#pragma once +#include + +#if MUI_CFG_ENABLE_V1DMRES + +#define DMResVer L"v1.0.1"; + +namespace Mui { + + //梦月资源类 资源键类型 + struct DMResKey + { + //数据资源 + UIResource res; + //块模式存储 + bool block = false; + //分块尺寸 + unsigned long blockSize = 0; + //文本资源 + std::wstring resText; + }; + + //资源类型 + enum ResType + { + AllRes,//全部资源 + DataRes,//数据资源 + StringRes//字符串资源 + }; + + struct DMEncBlockCallback + { + std::function callback = nullptr; + _m_param param = 0; + }; + + //梦月资源类 + //该类仅作为兼容和迁移旧项目使用 由于性能和安全问题 不建议再使用 请使用新的资源管理类DMResFile V2 需要在编译时定义MUI_CFG_ENABLE_V2DMRES + class DMResources + { + public: + ~DMResources(); + + /*资源读取*/ + + /*从文件初始化资源 资源仅初始化一次 多次返回false + * @param resfiles dmres文件路径 + * @param mem 是否使用内存加载模式 内存加载模式支持写入和修改 + */ + bool LoadResource(std::wstring resfiles, bool mem = false); + +#ifdef _WIN32 + //从WindowsPE读取资源 + static UIResource ReadPEResource(DWORD resID, LPCWSTR type); + + //从PE初始化资源 资源仅初始化一次 多次返回false + bool LoadResource(_m_ulong resid, _m_lpcwstr restype); +#endif // _WIN32 + + //从内存加载资源文件 + bool LoadResource(UIResource memres); + + //读取资源数量 + _m_size GetResourceCount(); + + /*读取资源 资源需要使用Release释放 + * @param resname - 资源名称 + * @param reskey - 资源秘钥 + * @param type - 资源类型 + * + * @return 资源数据 如果Type=AllRes那么返回的结构体都有数据 否则仅有数据或文本 + */ + DMResKey ReadResource(std::wstring resname, std::wstring reskey, ResType type = AllRes); + + /*读取分块数据资源 + * @param resname - 资源名称 + * @param reskey - 资源秘钥 + * @param offset - 指针偏移位置 + * @param len - 读取长度 + * @param out retlen - 返回已读取长度 + * + * @return 资源数据 需要使用Release释放 + */ + UIResource ReadResourceBlock(std::wstring resname, std::wstring reskey, _m_uint offset, _m_size len, _m_size* retlen); + + /*读取数据资源尺寸 + * @param resname - 资源名称 + * @param type - 资源类型 仅DataRes或StringRes + * + * @return DataRes为资源字节大小 StringRes为文本长度 AllRes返回0 + */ + _m_size ReadResourceSize(std::wstring resname, ResType type); + + /*读取资源块信息 + * @param resname - 资源名称 + * @param out - 块尺寸 + * + * @return 资源是否为块模式存储 + */ + bool ReadResBlockInfo(std::wstring resname, _m_size* blocksize); + + //枚举资源名称 + bool EnumResourceName(std::vector& nameList); + //获取类名 + _m_lpcwstr GetResClassName(); + + /*资源写入*/ + + //新建一个内存资源类 资源仅初始化一次 多次返回false 类名长度不能大于30 + bool CreateResource(std::wstring classname); + //添加一个资源 + bool AddResource(DMResKey res, std::wstring resname, std::wstring reskey, DMEncBlockCallback callback = DMEncBlockCallback()); + //重命名一个资源 + bool RenameResource(std::wstring resname, std::wstring newname); + //重命名资源类名 + void RenameClassName(std::wstring newname); + //修改一个资源 + bool ChangeResource(std::wstring resname, DMResKey newres, std::wstring reskey, + ResType type = AllRes, DMEncBlockCallback callback = DMEncBlockCallback()); + //删除一个资源 + bool DeleteResource(std::wstring resname); + //保存资源文件 将所有更改写入文件 + bool SaveResource(std::wstring filename); + + //关闭资源文件 成功后可以重新初始化资源 + bool CloseResource(); + + //读取文件 + UIResource ReadFiles(std::wstring filepath, bool string = false); + //写入文件 + bool WriteFiles(std::wstring filepath, UIResource res); + + private: + _m_byte* HexStringToBytes(std::string str); + std::string Sha256StringHex(std::wstring str); + std::wstring wchar16Towchar32(char16_t * wchar16, _m_uint len); + char16_t * wchar32Towchar16(wchar_t * wchar32, _m_uint len); + //加密资源 + UIResource Enciphering(UIResource res, std::wstring key); + UIResource EncipheringText(std::wstring text, std::wstring key); + //解密资源 + UIResource Deciphering(UIResource res, std::wstring key); + std::wstring DecipheringText(UIResource res, std::wstring key); + //编码块资源 + void EncBlockProc(UIResource res, std::wstring resname, std::wstring key, _m_uint blocksize, UIResource* ret, DMEncBlockCallback callback); + UIResource EncBlockResource(UIResource res, std::wstring resname, std::wstring key, _m_uint blocksize, DMEncBlockCallback callback); + + //加载资源列表 + bool LoadResList(bool file = false); + + //资源头 + struct DMResHeader + { + //资源类名 + std::wstring classname; + //资源签名 + std::wstring ressign; + //资源版本号 + std::wstring resver; + //资源数量 + _m_uint rescount = 0; + }; + + struct DMResItem + { + std::wstring resname;//资源名称 + UIResource res;//被加密过的资源 + bool block = false;//块资源模式 + _m_uint blockSize = 0;//块尺寸 + UIResource text;//被加密过的文本资源 + std::pair<_m_ulong, _m_uint> resPos;//资源在文件位置的指针 + std::pair<_m_ulong, _m_uint> strPos;//文本在文件位置的指针 + }; + + std::vector dmResList; + DMResHeader m_resheader; + UIResource m_resource; + FILE* m_files = nullptr; + + std::mutex mx; + }; + +} +#endif \ No newline at end of file diff --git a/MiaoUI/src/include/FileSystem/Mui_FileSystem.h b/MiaoUI/src/include/FileSystem/Mui_FileSystem.h new file mode 100644 index 0000000..547f73e --- /dev/null +++ b/MiaoUI/src/include/FileSystem/Mui_FileSystem.h @@ -0,0 +1,136 @@ +/** + * FileName: Mui_FileSystem.h + * Note: 基本文件系统操作 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-10-9 Create +*/ +#pragma once +#include + +namespace Mui::FS +{ + namespace UI + { + struct DlgFilter + { + //描述信息,扩展名 + std::wstring description, extension; + }; + /* 打开浏览文件对话框 + * @param [in] isOpen - 是否为打开文件 否则为保存文件对话框 + * @param [in] multiple - 是否支持多选文件 保存对话框不支持多选 + * @param [in] filter - 文件过滤器列表 例如 { L"AllFiles(*.*), L"*.*" },{ L"ImageFiles(*.png;*.jpg)", L"*.png;*.jpg" } + * @param [in] parentWndHandle - 父窗口句柄 + * @param [out] selectedFiles - 返回已选择的文件列表 + * @param [in] defExtName - 默认文件扩展名 + * + * @return 如果打开对话框失败或者用户关闭对话框 返回false + */ + extern bool MBrowseForFile(bool isOpen, bool multiple, const std::vector& filter, + _m_param parentWndHandle, std::vector& selectedFiles, std::wstring_view defExtName = {}); + } + + //获取当前程序运行所在目录 + std::wstring MGetCurrentDir(); + + /*读取文件 + * @param filepath - 文件路径 + * @param string - 是否为字符串类型 如果为true 则结尾长度+1 并添加 '\0' + * @return 如果成功返回内存资源 否则为空 + * @exception MErrorCode::IOError; MErrorCode::OutOfMemory + */ + UIResource MReadFile(std::wstring_view filepath, bool string = false); + + /*写入文件 + * @param filepath - 文件路径 + * @param res - 内存数据 + * @return 返回写入成功的字节数 + * @exception MErrorCode::IOError; + */ + _m_size MWriteFile(std::wstring_view filepath, UIResource res); + + /*打开文件(64位长度) + * @param filepath - 文件路径 + * @param mode - 打开模式 wb、rb、w、r、a、+ + * @return C API文件句柄 + * @exception MErrorCode::IOError; + */ + FILE* MOpenFile(std::wstring_view filepath, const char* mode); + + /*移动文件指针(64位长度) + * @param file - 使用OpenFile打开的文件指针 + * @param offset - 目标位置 + * @param origin - 移动起点 SEEK_SET、SEEK_CUR、SEEK_END + * @exception MErrorCode::IOError; MErrorCode::InvalidParameter + */ + void MSeekFile(FILE* file, _m_long64 offset, int origin = SEEK_SET); + + /*获取路径最后的文件名 + * @param path - 路径 可以为正斜杠或反斜杠 + * @return 例如 C:\\Test\\Path\\filename.txt 返回 filename.txt + */ + std::wstring MGetFileName(std::wstring_view path); + + /*枚举目录 + * @param path - 起始路径 + * @param [out] dirlist - 返回的目录列表 + * @param recursive - 递归获取所有子级目录 否则仅获取起始路径下的目录 + * @exception MErrorCode::STDError; MErrorCode::AccessDenied; MErrorCode::IOError + */ + void MEnumDirectory(std::wstring_view path, std::vector& dirlist, bool recursive = false); + + /*枚举文件 + * @param path - 起始目录路径 + * @param filter - 文件过滤器列表 例如 { L".txt", L".png" } 为空{}则输出所有的文件 + * @param [out] dirlist - 返回的文件路径列表 + * @param recursive - 递归获取所有子级目录下的文件 否则仅获取起始路径下的文件 + * @exception MErrorCode::STDError; MErrorCode::AccessDenied; MErrorCode::IOError + */ + void MEnumFiles(std::wstring_view path, const std::vector& filter, + std::vector& dirlist, bool recursive = false); + + /*删除文件 + * @param path - 文件路径 + * @return 如果删除成功 返回true,文件不存在 返回false + * @exception MErrorCode::STDError; MErrorCode::AccessDenied; MErrorCode::IOError + */ + bool MRemoveFile(std::wstring_view path); + + /*删除目录 + * @param path - 目录路径 + * @param removeAll - 是否删除非空目录及其子目录的所有的内容 + * @return 如果删除成功 返回true, 如果目录不存在返回false 如果removeAll=false且目录中有文件 返回false + * @exception MErrorCode::STDError; MErrorCode::AccessDenied; MErrorCode::IOError + */ + bool MRemoveDirectory(std::wstring_view path, bool removeAll = false); + + /*检查文件或目录是否存在 + * @param path - 路径 + * @param isdir - 是否检查目录 + * @return 如果文件或目录不存在 返回false + * @exception MErrorCode::STDError; MErrorCode::AccessDenied; MErrorCode::IOError + */ + bool MFileExists(std::wstring_view path, bool isdir = false); + + /*创建目录 + * @param path - 创建路径 + * @param mult - 是否创建多级目录 + * @return 如果成功或者目录已存在返回true 如果mult=false 且父目录不存在 返回false + * @exception MErrorCode::AccessDenied; MErrorCode::IOError + */ + bool MCreateDirectory(std::wstring_view path, bool mult); +} \ No newline at end of file diff --git a/MiaoUI/src/include/Manager/Mui_ControlMgr.h b/MiaoUI/src/include/Manager/Mui_ControlMgr.h new file mode 100644 index 0000000..89c7f9e --- /dev/null +++ b/MiaoUI/src/include/Manager/Mui_ControlMgr.h @@ -0,0 +1,601 @@ +/** + * FileName: Mui_ControlMgr.h + * Note: UI控件管理器声明 + * + * Copyright (C) 2022-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-3-8 Create +*/ + +#pragma once +#include +#include +#include +#include +#include +#include + +/*控件类名和注册方法调用 + * 用法 在控件类开头使用 定义控件在XML中的名称 和特殊属性: + * UICtrlDemo : UILabel + * { + * public: + * //有属性列表的情况: + * MCTRL_DEFINE + * ( + * L"UICtrlDemo", //这里是控件类名 + * //这里是特殊属性列表(AttribType类型) + * { CtrlMgr::AttribType::UIStyle, L"style" }, + * { CtrlMgr::AttribType::UIBitmap, L"icon" }, + * .... + * ); + * + * //无属性列表 直接使用MCTRL_DEFNAME + * MCTRL_DEFNAME(L"UICtrlDemo"); + * + * //有属性列表和继承控件的情况: + * MCTRL_DEFINE_EX + * ( + * L"UICtrlDemo", //这里是控件类名 + * //需要使用MCTRL_MAKE_ATTRIB生成属性列表 + * MCTRL_MAKE_ATTRIB( + * { CtrlMgr::AttribType::UIStyle, L"style" }, + * { CtrlMgr::AttribType::UIBitmap, L"icon" } + * ), + * //然后是继承的属性 + * MCTRL_BASE_ATTRIB(UILabel) + * .... + * ); + * + * //可选 定义事件槽 + * MCTRL_DEFSLOT + ( + CtrlMgr::EventSlot clicked;//鼠标左键单击 + .... + ); + * } + */ + +#define MCTRL_ATTRIB _mctrl_attrib_attrib + +//获取基类属性列表 +#define MCTRL_BASE_ATTRIB(base) base::MCTRL_ATTRIB + +//定义控件Element名称和注册方法 +#define MCTRL_DEFNAME(x) static constexpr auto ClassName = x; \ +virtual std::wstring GetClsName() const override { return ClassName; } \ +static void Register() + +//控件特殊属性 CtrlMgr::AttribType +#define MCTRL_MAKE_ATTRIB(...) Helper::ToArray({__VA_ARGS__}) + +/*定义控件 +* @param name - 控件在XML中的element名称 +* @param ... 特殊属性列表 +*/ +#define MCTRL_DEFINE(x,...) MCTRL_DEFNAME(x); \ +inline static constexpr std::array MCTRL_ATTRIB = Helper::MergeArrays(MCTRL_MAKE_ATTRIB(__VA_ARGS__)) + +/*定义控件 +* @param name - 控件在XML中的element名称 +* @param attrib - 特殊属性列表 使用MCTRL_MAKE_ATTRIB生成 +* @param ... 继承的控件属性列表 +*/ +#define MCTRL_DEFINE_EX(x,attrib,...) MCTRL_DEFNAME(x); \ +inline static constexpr std::array MCTRL_ATTRIB = Helper::MergeArrays(attrib, __VA_ARGS__) + +#define mslot mctrl_eventslot +//定义事件槽 +#define MCTRL_DEFSLOT(...) public: struct { __VA_ARGS__ } mslot + +/* 注册控件方法 + * 此宏自动添加了注册的属性列表 + * 否则也可以手动调用RegisterControl注册 + */ +#define MCTRL_REGISTER(method) CtrlMgr::RegisterControl(ClassName, method, Helper::ArrayToVector(MCTRL_ATTRIB)) + +namespace Mui::Ctrl +{ + template + constexpr bool mis_uinodecls = std::is_base_of_v + || std::is_base_of_v; + + template + Target* mcast_control(T* control) + { + //检查参数类型是合法的 + static_assert(mis_uinodecls, //目标的类型不是UINodeBase的基类或派生类 + "[MiaoUI cast]: The targettype is not a base or derived class of UINodeBase."); + + static_assert(mis_uinodecls, //目标的类型不是UINodeBase的基类或派生类 + "[MiaoUI cast]: The targettype is not a base or derived class of UINodeBase."); + + constexpr bool isBase = std::is_base_of_v; + constexpr bool isDerived = std::is_base_of_v; + + static_assert(isBase || isDerived, //参数和要转换的目标之间没有继承或派生关系。 + "[MiaoUI cast]: There is no inheritance or derivation relationship between the parameters and the target to be converted."); + +#ifdef _DEBUG + Target* ptr = dynamic_cast(control); + if (!ptr) //无法转换到目标类型 + throw std::exception("[MiaoUI cast]: Unable to convert to target type."); +#endif + return static_cast(control); + } +} + +namespace Mui::CtrlMgr +{ + namespace Attrib + { + template + std::wstring Value_Make2x(T a, T b) + { + return std::to_wstring(a) + L"," + std::to_wstring(b); + } + + template + std::wstring Value_Make4x(T a, T b, T c, T d) + { + return std::to_wstring(a) + L"," + std::to_wstring(b) + L"," + + std::to_wstring(c) + L"," + std::to_wstring(d); + } + + inline std::wstring Value_Make2x(UIPoint pt) + { + return Value_Make2x(pt.x, pt.y); + } + + inline std::wstring Value_Make2x(UISize size) + { + return Value_Make2x(size.width, size.height); + } + + inline std::wstring Value_Make4x(UIRect rect) + { + return Value_Make4x(rect.left, rect.top, rect.right, rect.bottom); + } + + template + std::wstring Value_Make4x(_m_rect_t rect) + { + return Value_Make4x(rect.left, rect.top, rect.right, rect.bottom); + } + } + + typedef std::function RegMethod; + + struct AttribType + { + enum type + { + defaults, //默认文本类型 + labelStyle, //UILabel::Attribute*属性类型 + listfontStyle, //UIListBox::ItemFont*属性类型 + UIStyle, //UIStyle*属性类型 + UIResource, //UIResource*属性类型 + UIBitmap //UIBitmap属性类型 + } attribType = defaults; + std::wstring_view attribName; + }; + + /*inline std::vector MergeAttrib(const std::initializer_list>& lists) + { + std::vector ret; + for (const auto& list : lists) + { + ret.insert(ret.end(), list.begin(), list.end()); + } + return ret; + }*/ + + + class AttribConverter + { + public: + /* 默认转换器 + * 支持类型: + * + * bool + * float + * double + * + * std::wstring + * std::wstring_view + * wchar_t + * + * _m_uchar + * _m_byte + * _m_short + * _m_ushort + * _m_int + * _m_long + * _m_uint + * _m_ulong + * _m_color + * _m_param + * _m_long64 + * _m_ulong64 + * _m_size + * _m_ptr + * _m_ptrv + * + * _m_rect + * _m_rect_t + * + * UIRect + * UIPoint + * UISize + * + * void* + * + * UIStylePtr + * UIStyle* + * UIBitmapPtr + * UIBitmap* + */ + AttribConverter(); + virtual ~AttribConverter() = default; + + template + T Convert(std::wstring_view value) + try + { + auto iter = m_methodList.find(typeid(T).name()); + if (iter == m_methodList.end()) + return {}; + return std::any_cast(iter->second.get(value)); + } + catch(...) + { +#ifdef _DEBUG + _M_OutErrorDbg_(L"不支持的类型转换", false); +#endif + return {}; + } + + template + std::wstring Convert(const T& value) + try + { + auto iter = m_methodList.find(typeid(T).name()); + if (iter == m_methodList.end()) + return {}; + return iter->second.set(value); + } + catch(...) + { +#ifdef _DEBUG + _M_OutErrorDbg_(L"不支持的类型转换", false); +#endif + return {}; + } + + using ConversionMethod_Get = std::function; + using ConversionMethod_Set = std::function; + + protected: + //添加转换方法 + template + void AddMethod(ConversionMethod_Get get, ConversionMethod_Set set) + { + m_methodList.insert(std::make_pair((std::string)typeid(T).name(), data{ std::move(get), std::move(set)})); + } + + private: + struct data + { + ConversionMethod_Get get = nullptr; + ConversionMethod_Set set = nullptr; + }; + static std::unordered_map m_methodList; + }; + + //member, name, seter, geter +#define MakeUIAttrib(__struct, ...) decltype(__struct)::MakeAttrib(__VA_ARGS__) + //member(final), offset, name, seter, geter +#define MakeUIAttribEx(__struct, member, offset, ...) decltype(__struct)::MakeAttribAtOffset(member, decltype(__struct)::MOffsetOf(offset), __VA_ARGS__) + + //在赋值属性之后调用Set方法回调 +#define MakeUIAttrib_AfterSetOnly(__struct, member, name, paramname, code) decltype(__struct)::MakeAttrib(member, name, \ + [](decltype(__struct)::SetData param) \ + { \ + param.Assign(member); \ + return [](decltype(__struct)::SetData& paramname) -> bool \ + code(paramname); \ + }) + + //在赋值属性之后调用Get方法回调 +#define MakeUIAttrib_AfterGetOnly(__struct, member, name) decltype(__struct)::MakeAttrib(member, name, nullptr, \ + [](decltype(__struct)::GetData param) \ + { \ + code \ + } \ + + //只转换指针类型 不使用AttribConverter类 能减少一些编译代码体积 这不会检查类型是否匹配 + template + T* ConvertAttribPtr(std::wstring_view attrib) + { + return reinterpret_cast(Helper::M_StoULong64(attrib)); + } + + template + std::wstring ConvertAttribPtr(T* attrib) + { + return std::to_wstring((_m_ptrv)attrib); + } + + template + class UIAttribute + { + public: + struct SetData + { + T* data = nullptr; + AttribConverter* converter = nullptr; + std::wstring_view attribName; + std::wstring_view attribValue; + Param param; + + //将属性转换到目标类型 + template + inline Type GetValue() + { + //退化成void*来转换 + if constexpr (std::is_pointer_v) + return (Type)ConvertAttribPtr(attribValue); + //字符串无需转换 用不着再跑一遍Convert类 + else if constexpr (std::is_same_v) + return attribValue.data(); + else if constexpr (std::is_same_v || std::is_same_v) + return attribValue; + else if constexpr (std::is_enum_v) + return (Type)converter->Convert(attribValue); + else + return converter->Convert(attribValue); + } + + //对属性赋值 将attribValue的值转换并赋值到绑定的结构体成员 + template + void Assign(Member Struct::* member) + { + auto pData = (Member*)(_m_ptrv(data) + MOffsetOf(member)); + *pData = GetValue(); + } + + template + void AssignAtOffset(Member Struct::* member, _m_ptrv offset) + { + auto pData = (Member*)(_m_ptrv(data) + MOffsetOf(member) + offset); + *pData = GetValue(); + } + }; + + struct GetData + { + T* data = nullptr; + AttribConverter* converter = nullptr; + std::wstring_view attribName; + Param param; + }; + + using SetAttribCallback = std::function; + using GetAttribCallback = std::function; + + struct attribCallback + { + SetAttribCallback set = nullptr; + std::function get = nullptr; + }; + + UIAttribute(T attrib, const std::shared_ptr& converter = std::make_shared()) + : m_attribData(std::move(attrib)), m_converter(converter) {} + + /*设置属性 + * @param attribName - 属性名 + * @param attrib - 属性值 (*如果指定类型则必须与源类型匹配) + * @param param - 附加参数 + */ + template + bool SetAttribute(std::wstring_view attribName, const Type& attrib, Param param = {}) + { + auto iter = m_attribCallbackList.find(attribName.data()); + if (iter == m_attribCallbackList.end() || !iter->second.set) + return false; + + bool ret; + if constexpr (std::is_same_v) + ret = iter->second.set({ &m_attribData, m_converter.get(), attribName, attrib, std::move(param) }); + else + { + std::wstring value = m_converter->Convert(attrib); + ret = iter->second.set({ &m_attribData, m_converter.get(), attribName, value, std::move(param) }); + } + + return ret; + } + + //获取属性 (*如果指定类型则必须与源类型匹配) + //@return 如果找到属性 返回true + template + bool GetAttribute(std::wstring_view attribName, Type& dstValue, Param param = {}) + { + auto iter = m_attribCallbackList.find(attribName.data()); + if (iter == m_attribCallbackList.end() || !iter->second.get) + return false; + + auto ret = iter->second.get({ &m_attribData, m_converter.get(), attribName, std::move(param) }); + + if constexpr (std::is_same_v) + dstValue = ret; + + dstValue = m_converter->Convert(ret); + return true; + } + + //注册设置方法 + static void RegisterAttrib(const std::vector>& attribList) + { + for (auto& attrib : attribList) + m_attribCallbackList.insert(std::make_pair(attrib.first, attrib.second)); + } + + template + static void RegisterAttrib(Args&&... args) + { + (void)std::initializer_list{ (m_attribCallbackList.insert(std::forward(args)), 0) ... }; + } + + template + static void RegisterAttrib(std::initializer_list& args) + { + for (const auto& arg : args) + { + m_attribCallbackList.insert(arg); + } + } + + /*生成属性绑定 + * @param member - 要绑定成员的变量 + * @param name - 属性文本名称 + * @param set - 设置属性回调 属性被设置时调用 可为空 默认nullptr 属性会自动赋值,如果指定回调,需要在回调手动给变量赋值 + * @param get - 获取属性回调 获取属性时调用 可为空 默认nullptr + */ + template + static auto MakeAttrib(Member Struct::* member, std::wstring_view name, SetAttribCallback set = nullptr, GetAttribCallback get = nullptr) + { + auto _set = [_callback = set, member](SetData data) -> bool + { + if (_callback) + return _callback(std::move(data)); + + data.template Assign(member); + + return true; + }; + auto _get = [_callback = get, member](GetData data) -> std::wstring + { + auto pData = (Member*)(_m_ptrv(data.data) + MOffsetOf(member)); + + if (_callback) + _callback(data); + + if constexpr (std::is_pointer_v) + return data.converter->Convert((void*)*pData); + else if constexpr (std::is_enum_v) + return data.converter->Convert((int)*pData); + else + return data.converter->Convert(*pData); + }; + return std::make_pair(std::wstring{ name }, attribCallback{ std::move(_set), std::move(_get) }); + } + + /*生成属性绑定从附加偏移量 + * @param member - 要绑定的最终成员类型 + * @param offset - 结构体成员偏移量(不包括最终成员) + * @param name - 属性文本名称 + * @param set - 设置属性回调 属性被设置时调用 可为空 默认nullptr 属性会自动赋值,如果指定回调,需要在回调手动给变量赋值 + * @param get - 获取属性回调 获取属性时调用 可为空 默认nullptr + */ + template + static auto MakeAttribAtOffset(Member Struct::* member, _m_ptrv offset, std::wstring_view name, SetAttribCallback set = nullptr, GetAttribCallback get = nullptr) + { + auto _set = [_callback = set, member, offset](SetData data) -> bool + { + if (_callback) + return _callback(std::move(data)); + + data.template AssignAtOffset(member, offset); + + return true; + }; + auto _get = [_callback = get, member, offset](GetData data) -> std::wstring + { + auto pData = (Member*)(_m_ptrv(data.data) + MOffsetOf(member) + offset); + + if (_callback) + _callback(data); + + if constexpr (std::is_pointer_v) + return data.converter->Convert((void*)*pData); + else if constexpr (std::is_enum_v) + return data.converter->Convert((int)*pData); + else + return data.converter->Convert(*pData); + }; + return std::make_pair(std::wstring{ name }, attribCallback{ std::move(_set), std::move(_get) }); + } + + template + static constexpr _m_ptrv MOffsetOf(Member Struct::* member) + { + return _m_ptrv(&((Struct*)nullptr->*member)); + } + + //直接访问数据 + [[nodiscard]] const T& Get() const { return m_attribData; } + T& Set() { return m_attribData; } + T* GetPtr() { return &m_attribData; } + void Set(const T& data) { m_attribData = data; } + void Set(T&& data) { m_attribData = std::move(data); } + + using DataType = T; + private: + DataType m_attribData; + std::shared_ptr m_converter; + static std::unordered_map m_attribCallbackList; + }; + template + std::unordered_map::attribCallback> UIAttribute::m_attribCallbackList; + + template + class EventSlot + { + public: + using ProcType = std::function; + + void Bind(ProcType slot) + { + m_slotList.emplace_back(std::move(slot)); + } + + template + void Emit(Args... args) + { + for (auto& slot : m_slotList) + slot(std::forward(args)...); + } + + private: + std::vector m_slotList; + }; + + /* 注册控件 注册后可以使用MuiXML类创建 + * @param name - 控件类名 在XML中的名称 + * @param method - 创建方法 + * @param ptrAttrib - 特殊属性列表 + * 特殊属性列表: + * 指定为特殊属性的属性将在SetAttribute时将value转换成对应类型的指针传递给字符串 + * @return 如果重复注册或者已有重名将失败 + */ + bool RegisterControl(std::wstring_view name, RegMethod&& method, const std::vector& ptrAttrib = {}); + + void RegisterMuiControl(); + + Ctrl::UIControl* CreateControl(std::wstring_view name, Ctrl::UIControl* parent); + + AttribType::type GetAttributeType(std::wstring_view name, std::wstring_view attribName); +} diff --git a/MiaoUI/src/include/Manager/Mui_ResourceMgr.h b/MiaoUI/src/include/Manager/Mui_ResourceMgr.h new file mode 100644 index 0000000..bdcd8f9 --- /dev/null +++ b/MiaoUI/src/include/Manager/Mui_ResourceMgr.h @@ -0,0 +1,251 @@ +/** + * FileName: Mui_ResourceMgr.h + * Note: UI资源管理器 + * + * Copyright (C) 2023-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2023-12-7 Create +*/ +#pragma once +#include +#include +#include +#define MUIRESFILE std::pair + +namespace Mui +{ + enum class UIStyleType + { + Null, + Image, + Geometry + }; + + class UIResourceMgr; + + namespace Window { class UIWindowBasic; } + + class UIBitmap : public RAII::MBasicObj + { + public: + UIBitmap(UIBitmap&&) = delete; + UIBitmap(const UIBitmap&) = delete; + UIBitmap& operator=(const UIBitmap&) = delete; + UIBitmap& operator=(UIBitmap&&) = delete; + + Render::Def::MBitmapPtr GetBitmap() const { return m_bitmap; } + + protected: + UIBitmap() = default; + Render::Def::MBitmapPtr m_bitmap = nullptr; + + friend class UIResourceMgr; + }; + + using UIBitmapPtr = RAII::Mui_Ptr; + + class UIStyle : public RAII::MBasicObj + { + public: + UIStyle(UIStyle&&) = delete; + UIStyle(const UIStyle&) = delete; + UIStyle& operator=(const UIStyle&) = delete; + UIStyle& operator=(UIStyle&&) = delete; + + [[nodiscard]] UIStyleType GetType() const { return m_type; } + [[nodiscard]] _m_ushort GetPartCount() const { return m_partCount; } + [[nodiscard]] _m_rect_t GetNineGridPath() const { return m_gridPath; } + [[nodiscard]] bool IsNineGrid() const { return m_nineGrid; } + + //scale属性仅针对Geometry样式的线宽和圆角度缩放 不影响frame + //@exception MErrorCode::InvalidParameter + virtual void PaintStyle(Render::MRenderCmd* render, MPCRect dest, + _m_byte alpha, int state, _m_ushort count, _m_scale scale = { 1.f, 1.f }) = 0; + + protected: + UIStyle() = default; + UIStyleType m_type = UIStyleType::Null; + + _m_ushort m_partCount = 0; + + bool m_nineGrid = false; + _m_rect_t m_gridPath = { 0 }; + + friend class UIResourceMgr; + }; + + using UIStylePtr = RAII::Mui_Ptr; + + class UIResourceMgr final + { + public: + explicit UIResourceMgr(Render::MRenderCmd* render) : m_render(render) {} + UIResourceMgr(UIResourceMgr&&) = delete; + UIResourceMgr(const UIResourceMgr&) = delete; + UIResourceMgr& operator=(const UIResourceMgr&) = delete; + UIResourceMgr& operator=(UIResourceMgr&&) = delete; + ~UIResourceMgr(); + + bool AddResourcePath(std::wstring_view path, std::wstring_view key); + bool AddResourceMem(UIResource memfile, std::wstring_view key); + + //删除资源文件引用 如果有正在使用的共享的资源 这将导致共享资源在需要重载时无法加载 + bool RemoveResource(std::wstring_view path); + + /* 从已加载的Style列表中查找 + * @param name - Style名称 + * @return UIStyle* 该指针由资源管理器管理 不可释放 + */ + UIStyle* FindStyle(std::wstring_view name); + + /* 从已添加的所有资源文件中加载Style到列表 + * 已有或重名的不会重复加载 + * @exception MErrorCode::InvalidParameter + */ + void LoadStyleList(); + + /* 从资源文件中加载Style到列表 + * @param resname - 在资源文件中的名称 + * @param out dst - 输出UIStyle指针 可为空 不接收 (默认nullptr) + * @return 如果找不到资源项目或者列表已存在同名资源将失败(false) + */ + bool LoadStyle(std::wstring_view resname, UIStyle** dst = nullptr); + + /* 从内存创建ImageStyle 并添加到列表 + * @param name - Style名称 + * @param memimg - 内存图像资源 + * @param count - Style Part数 + * @param out dst - 输出UIStyle指针 可为空 不接收 (默认nullptr) + * @param nineGrid - 是否为九宫格素材 (默认false) + * @param gridpath - 宫格Rect (默认0) + * @return 重名或资源无效将导致失败(false) + */ + bool AddImageStyle(std::wstring_view name, UIResource memimg, _m_ushort count, + UIStyle** dst = nullptr, bool nineGrid = false, _m_rect_t gridpath = { 0 }); + + /* 从xml创建图形命令Style 并添加到列表 + * @param name - Style名称 + * @param xml - 图形命令xml代码 + * @param out dst - 输出UIStyle指针 可为空 不接收 (默认nullptr) + * @return 重名或xml代码格式无效将导致失败(false) + */ + bool AddGeometryStyle(std::wstring_view name, std::wstring_view xml, UIStyle** dst = nullptr); + + /* 从列表移除Style + * 此函数将Style从资源管理器的列表移除 不再管理 需要手动调用MSafeRelease释放资源 + * @return 返回目标Style指针 + */ + [[nodiscard]] UIStyle* RemoveStyle(std::wstring_view resname); + + /* 从资源文件中读取指定资源UIResource + * @param name - 资源名称 + * @return 如果资源读取成功 需要使用Release释放资源 + */ + UIResource ReadResource(std::wstring_view name); + + /* 从资源文件创建独立UI位图 + * 删除资源文件引用时也不影响位图重建 + * @param - name - 资源名称 + * @return 如果资源读取成功 返回位图指针 否则返回nullptr + */ + UIBitmapPtr CreateUniqueUIBitamp(std::wstring_view name); + + /* 从内存资源创建独立UI位图 + * 删除资源文件引用时也不影响位图重建 + * @param name - 资源名称 + * @return 如果资源读取成功 返回位图指针 否则返回nullptr + */ + UIBitmapPtr CreateUniqueUIBitamp(UIResource res); + + /* 从资源文件创建共享UI位图 + * @param - name 资源名称 + * @return 如果资源读取成功 返回位图指针 否则返回nullptr + * 多次创建同一资源名将只返回同一对象 共同使用 + */ + UIBitmapPtr CreateSharedUIBitmap(std::wstring_view name); + + private: + Render::MRenderCmd* m_render = nullptr; + struct resfile + { + MUIRESFILE file; + std::wstring path; + }; + std::vector m_resList; + + struct StyleData + { + UIStylePtr style = nullptr; + bool memres = false; + std::wstring srcname; + }; + std::unordered_map m_styleList; + std::unordered_map m_sharedBmpList; + + bool LoadStyleFromDMRes(MUIRESFILE& file, std::wstring_view name, std::wstring& dst, + bool res = false, UIResource* dstres = nullptr); + + bool LoadStyleInternal(MUIRESFILE& file, std::wstring_view name, UIStyle** dststyle); + + bool AddImageStyleInternal(UIResource memimg, _m_ushort count, + UIStyle** dst = nullptr, bool nineGrid = false, _m_rect_t gridpath = { 0 }); + + bool AddGeometryStyleInternal(std::wstring_view xml, UIStyle** dst = nullptr); + + void FreeResFile(MUIRESFILE& file); + + friend class UIStyle; + friend class UIMgrResource; + friend class UIBitmapShared; + friend class UIBitmapSVG; + friend class Window::UIWindowBasic; + }; + + class UIStyleImage final : public UIStyle + { + protected: + void PaintStyle(Render::MRenderCmd* render, MPCRect dest, + _m_byte alpha, int state, _m_ushort count, _m_scale scale) override; + + UIBitmapPtr m_bitmap = nullptr; + + friend class UIResourceMgr; + }; + + class UIStyleGeometry final : public UIStyle + { + protected: + void PaintStyle(Render::MRenderCmd* render, MPCRect dest, + _m_byte alpha, int state, _m_ushort count, _m_scale scale) override; + + void InitResource(Render::MRenderCmd* render); + + struct CMD + { + int type = 0; + _m_rect_t m_dst = { 0 }; + _m_byte m_rctype[4] = { 0 }; + _m_color color = 0; + float param = 0.f; + int width = 0; + }; + + Render::Def::MPenPtr m_pen = nullptr; + Render::Def::MBrushPtr m_brush = nullptr; + std::vector> m_cmdlist; + + friend class UIResourceMgr; + }; +} \ No newline at end of file diff --git a/MiaoUI/src/include/Mui.h b/MiaoUI/src/include/Mui.h new file mode 100644 index 0000000..1762fe9 --- /dev/null +++ b/MiaoUI/src/include/Mui.h @@ -0,0 +1,76 @@ +/** + * FileName: Mui.h + * Note: MiaoUI Lite library 声明 + * + * ___ ___ _ _ _ _____ + * | .\/. |(_) __ _ ___ | | | ||_ _| + * | |\/| || | / _` | / _ \ | | | | | | + * | | | || || (_| || (_) || |_| | _| |_ + * \_| |_/|_| \__,_| \___/ \___/ \___/ + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-17 Create +*/ + +#pragma once +//界面助手 +#include +//界面库扩展设置 +#include +//控件 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//控件管理器 用于注册和通过类名创建控件 +#include + +#include +#include + +//用户助手类 +#include +//文件系统 +#include + +//Windows库引入 +#ifdef _DEBUG +#ifdef _WIN32 +#ifdef _WIN64 +#pragma comment(lib, "MiaoUILite64d.lib") +#else +#pragma comment(lib, "MiaoUILite32d.lib") +#endif //_WIN64 +#endif //_WIN32 + +#else +#ifdef _WIN32 +#ifdef _WIN64 +#pragma comment(lib, "MiaoUILite64.lib") +#else +#pragma comment(lib, "MiaoUILite32.lib") +#endif //_WIN64 +#endif //_WIN32 +#endif // DEBUG \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_Base.h b/MiaoUI/src/include/Mui_Base.h new file mode 100644 index 0000000..6747e42 --- /dev/null +++ b/MiaoUI/src/include/Mui_Base.h @@ -0,0 +1,631 @@ +/** + * FileName: Mui_Base.h + * Note: 基本文件和类型定义 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-17 Create +*/ +#pragma once +#include +#include +#include +#include + +namespace Mui +{ + using namespace std::chrono; + + /*计时器回调 + * window ptr + * id + * time + */ + using MTimerCallback = std::function; + + //DPI缩放值 + struct _m_scale + { + float cx = 0.f; + float cy = 0.f; + }; + + //Rect缩放值 + struct _m_rcscale + { + float xs = 0.f; + float ys = 0.f; + float ws = 0.f; + float hs = 0.f; + + [[nodiscard]] _m_scale scale() const + { + return { ws, hs }; + } + }; + + template + T _scale_to(T src, float s) + { + if constexpr(std::is_floating_point_v) + return src * s; + else + return (T)round(s * (float)src); + } + + union _m_color + { + struct + { + _m_byte r; + _m_byte g; + _m_byte b; + _m_byte a; + }; + _m_uint argb; + + constexpr _m_color(_m_uint color) : argb(color) {} + _m_color() = default; + + operator _m_uint() const { return argb; } + + /* 从十六进制文本创建颜色 RRGGBBAA 不包括#字符 + * @param alpha - 输入是否包含alpha值 否则为RRGGBB alpha值默认FF + */ + _m_color(std::wstring_view hex, bool alpha = true); + + + /* 获取十六进制颜色文本 RRGGBBAA 不包括#字符 + * @param alpha - 是否包含alpha值 否则为RRGGBB + */ + std::wstring HEX(bool alpha = true); + + //Alpha混合 返回混合后的颜色 + static _m_byte AlphaBlend(_m_byte dst, _m_byte src); + }; + + using UIColor = _m_color; + + template + struct _m_rect_t + { + T left; + T top; + T right; + T bottom; + _m_rect_t(T x, T y, T x2, T y2) + { + left = x; + top = y; + right = x2; + bottom = y2; + } + + _m_rect_t(T xywh) + { + left = top = right = bottom = xywh; + } + + _m_rect_t() + { + left = top = right = bottom = {}; + } + + [[nodiscard]] _m_rect_t scale(float x, float y) const + { + return + { + _scale_to(left, x), + _scale_to(top, y), + _scale_to(right, x), + _scale_to(bottom, y) + }; + } + + [[nodiscard]] _m_rect_t scale(_m_scale _scale) const + { + return scale(_scale.cx, _scale.cy); + } + + template + [[nodiscard]] _m_rect_t ToRectT() const + { + return { (Target)left, (Target)top, (Target)right, (Target)bottom }; + } + + template + [[nodiscard]] _m_rect_t ToRectTAsRound() const + { + return { (Target)round(left), (Target)round(top), (Target)round(right), (Target)round(bottom) }; + } + + [[nodiscard]] T GetWidth() const + { + return right - left; + } + + [[nodiscard]] T GetWidthAsFloor() const + { + return floor(right) - floor(left); + } + + [[nodiscard]] T GetHeight() const + { + return bottom - top; + } + + [[nodiscard]] T GetHeightAsFloor() const + { + return floor(bottom) - floor(top); + } + + bool IsEmpty() + { + return left == 0 && top == 0 && right == 0 && bottom == 0; + } + }; + + using _m_rect = _m_rect_t; + using _m_rectf = _m_rect_t; + + template + struct _m_point_t + { + T x = 0; + T y = 0; + + [[nodiscard]] _m_point_t scale(float _x, float _y) const + { + return { _scale_to(x, _x), _scale_to(y, _y) }; + } + + [[nodiscard]] _m_point_t scale(_m_scale _scale) const + { + return scale(_scale.cx, _scale.cy); + } + + bool operator==(const _m_point_t& pt) const + { + if constexpr(std::is_floating_point_v) + return std::abs(x - pt.x) < std::numeric_limits::epsilon() && std::abs(y - pt.y) < std::numeric_limits::epsilon(); + else + return x == pt.x && y == pt.y; + } + + bool operator!=(const _m_point_t& pt) const + { + return !operator==(pt); + } + }; + + using _m_pointf = _m_point_t; + + template + struct _m_size_t + { + T width = 0; + T height = 0; + + [[nodiscard]] _m_size_t scale(float _ws, float _hs) const + { + return { _scale_to(width, _ws), _scale_to(height, _hs) }; + } + + [[nodiscard]] _m_size_t scale(_m_scale _scale) const + { + return scale(_scale.cx, _scale.cy); + } + + bool operator==(const _m_size_t& pt) const + { + if constexpr (std::is_floating_point_v) + return std::abs(width - pt.width) < std::numeric_limits::epsilon() && std::abs(height - pt.height) < std::numeric_limits::epsilon(); + else + return width == pt.width && height == pt.height; + } + + bool operator!=(const _m_size_t& pt) const + { + return !operator==(pt); + } + }; + + using _m_sizef = _m_size_t; + + //矩形 + class UIRect : public _m_rect_t + { + public: + UIRect(); + UIRect(const _m_rect& src); + UIRect(int x, int y, int width, int height); + + [[nodiscard]] _m_rect ToRect() const; + template + [[nodiscard]] _m_rect_t ToRectT() const + { + return { (T)left, (T)top, (T)right, (T)bottom }; + } + void SetWidth(int width); + void SetHeight(int height); + [[nodiscard]] bool IsEmpty() const; + void Empty(); + void Join(const UIRect& rc); + void ResetOffset(); + void Normalize(); + void Offset(int cx, int cy); + void Inflate(int cx, int cy); + void Deflate(int cx, int cy); + void Union(const UIRect& rc); + + bool operator==(const UIRect& rc) const; + bool operator!=(const UIRect& rc) const; + + private: + int offsetx = 0; + int offsety = 0; + }; + + //尺寸 + using UISize = _m_size_t; + + //二维坐标 + using UIPoint = _m_point_t; + + using MPCRect = const _m_rect*; + using MPCSize = const UISize*; + using MPCPoint = const UIPoint*; + + //内存资源 + struct UIResource + { + _m_byte* data = nullptr; + _m_size size = 0; + + UIResource() = default; + UIResource(_m_byte* p, _m_size s) { data = p; size = s; } + void Release() { delete[] data; size = 0; data = nullptr; } + + explicit operator bool() const noexcept + { + return data && size; + } + + //复制内存数据 + [[nodiscard]] UIResource Copy() const + { + if (!operator bool()) return {}; + const UIResource ret(new _m_byte[size], size); + memcpy(ret.data, data, size); + return ret; + } + }; + + //字体样式 + struct UIFontStyle + { + bool bold = false; //加粗 + bool italics = false; //倾斜 + bool underline = false; //下划线 + bool strikeout = false; //删除线 + + bool operator== (const UIFontStyle& font) const; + bool operator!= (const UIFontStyle& font) const; + }; + + //UI文本 + class UIString + { + public: + UIString(); + UIString(_m_lpcwstr pstr); + UIString(std::wstring_view strview); + UIString(const std::wstring& string); + UIString(UIString&&) noexcept; + UIString(const UIString&); + UIString& operator=(const UIString&); + UIString& operator=(UIString&&) noexcept; + ~UIString(); + + bool operator== (_m_lpcwstr other) const; + bool operator!= (_m_lpcwstr other) const; + bool operator== (const UIString& other) const; + bool operator!= (const UIString& other) const; + bool operator== (std::wstring_view other) const; + bool operator!= (std::wstring_view other) const; + + [[nodiscard]] _m_size length() const { return m_length; } + [[nodiscard]] bool empty() const { return m_length == 0; } + [[nodiscard]] _m_lpcwstr cstr() const { return m_string.get(); } + [[nodiscard]] std::wstring_view view() const { return m_string.get(); } + + + private: + void CopyForm(_m_lpcwstr pstr); + std::unique_ptr m_string = nullptr; + _m_size m_length = 0; + }; + + namespace Ctrl { class UIControl; } + struct UIFocus + { + Ctrl::UIControl* curFocus = nullptr; + }; + + //[线程安全] + //单例线程类 一般用于任务线程 + template + class MThreadT + { + public: + MThreadT(std::function threadproc) + { + m_stop = true; + m_proc = std::move(threadproc); + } + virtual ~MThreadT() { Stop(); } + + //创建线程 @param pause - 是否暂停线程 + void Start(bool pause = false) + { + if (!m_stop) return; + m_pause = pause; + m_stop = false; + m_thread = new std::thread(&MThreadT::Thread, this); + } + + /*暂停线程 + * @param now - 如果在线程内调用 是否立即暂停 否则仅设置flag 等待threadproc返回后才会暂停 + */ + void Pause(bool now = false) + { + std::unique_lock lock(m_mutex); + m_pause.store(true, std::memory_order_relaxed); + //线程内调用直接暂停 + if (now && std::this_thread::get_id() == GetID()) + { + Wait(lock); + } + } + + /*暂停线程 + * @param lock - 提供用于条件变量的锁 如果在线程内调用 立即暂停 否则仅设置flag 等待threadproc返回后才会暂停 + */ + void Pause(std::unique_lock& lock) + { + m_pause.store(true, std::memory_order_relaxed); + if (std::this_thread::get_id() == GetID()) + Wait(lock); + } + + //还原线程 + //@param wait - 是否等待线程成功被唤醒后才返回 + void Resume(bool wait = false) + { + auto lock = GetLock(); + if (!IsPause() || !IsRuning()) return; + + m_pause = false; + + if (wait) + { + std::promise notify; + auto waitnotify = notify.get_future(); + { + m_waitList.emplace_back(¬ify); + lock.unlock(); + } + m_condition.notify_all(); + waitnotify.wait(); + } + else + m_condition.notify_all(); + } + + //关闭线程 线程被关闭后才会返回 + void Stop() + { + if (!m_thread) return; + + m_stop = true; + m_pause = false; + m_condition.notify_all(); + m_thread->join(); + delete m_thread; + m_thread = nullptr; + } + + //获取线程ID + std::thread::id GetID() const + { + if (m_thread) + return m_thread->get_id(); + return {}; + } + + //是否暂停中 + bool IsPause() const + { + return m_pause; + } + + //是否运行中 + bool IsRuning() const + { + return !m_stop; + } + + //获取锁 + auto GetLock() + { + return std::unique_lock(m_mutex); + } + + private: + void Thread() + { + while (IsRuning()) + { + { + std::unique_lock lock(m_mutex); + Wait(lock); + } + m_proc(); + } + } + + void Wait(std::unique_lock& lock) + { + //等待唤醒 + while(IsPause()) + m_condition.wait(lock); + + for (auto& w : m_waitList) + w->set_value(); + m_waitList.clear(); + } + + std::function m_proc = nullptr; + std::thread* m_thread = nullptr; + std::condition_variable_any m_condition; + T m_mutex; + std::atomic_bool m_pause, m_stop; + std::vector*> m_waitList; + }; + using MThread = MThreadT; + + //[线程安全] + //定时器类 + class MTimers : MThread + { + public: + MTimers(); + ~MTimers() override; + + using CallBack = std::function; + using ID = _m_ptrv; + + /*添加定时器任务 + * @param elapse - 定时器循环周期 毫秒为单位 + * @param callback - 定时器回调函数 + * + * @return 返回定时器ID + */ + ID AddTimer(_m_uint elapse, CallBack callback); + + /*删除定时器任务 + * @param id - 自定义定时器ID + * + * @return 如果id不存在 则失败 + */ + bool DelTimer(ID id); + + private: + struct timer + { + _m_ptrv tid = 0; + _m_ulong time = 0; + _m_uint elapse = 0; + CallBack callback; + }; + using timerID = std::pair<_m_param, ID>; + std::map m_timerList; + std::unordered_map m_idList; + std::mutex m_lock; + + _m_param GetMilliSeconds(); + _m_param GetSleepTime(); + + void ThreadProc(); + }; + + //FPS计数器 + class MFPSCounter + { + public: + //计算FPS + _m_uint CalcFPS(); + //设置最大FPS -1=无限制 + void SetMaxFPS(float fps); + //获取最大FPS + float GetMaxFPS(); + //限制FPS + void LimitFPS(); + + private: + float fps = 0.0f; + float fpstime = -1.f; + _m_uint frameCount = 0; + steady_clock::time_point lastTime = steady_clock::now(); + steady_clock::time_point curTime = steady_clock::now(); + steady_clock::time_point m_BeginFrame = steady_clock::now(); + steady_clock::time_point m_EndFrame = steady_clock::now(); + _m_uint frame_count_per_second = 0; + time_point + prev_time_in_seconds = time_point_cast(m_BeginFrame); + steady_clock::duration m_fpsLimit = {}; + }; + + //[线程安全] + //队列 + template + class MQueue + { + std::deque m_queue; + std::mutex m_mutex; + public: + MQueue() = default; + + bool empty() + { + std::unique_lock lock(m_mutex); + return m_queue.empty(); + } + + auto size() + { + std::unique_lock lock(m_mutex); + return m_queue.size(); + } + + void insertquque(T& t) + { + std::unique_lock lock(m_mutex); + m_queue.push_front(t); + } + + void enqueue(T& t) + { + std::unique_lock lock(m_mutex); + m_queue.push_back(t); + } + + bool dequeue(T& t) + { + std::unique_lock lock(m_mutex); + if (m_queue.empty()) + return false; + t = std::move(m_queue.front()); + m_queue.pop_front(); + return true; + } + + void clear() + { + std::unique_lock lock(m_mutex); + m_queue.clear(); + } + }; +} \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_Config.h b/MiaoUI/src/include/Mui_Config.h new file mode 100644 index 0000000..b16a303 --- /dev/null +++ b/MiaoUI/src/include/Mui_Config.h @@ -0,0 +1,60 @@ +/** + * FileName: Mui_Config.h + * Note: MiaoUI库编译配置选项 + * + * Copyright (C) 2023-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2023-7-12 Create +*/ +#pragma once + +#ifndef __MUI__CONFIG__ +#define __MUI__CONFIG__ + +/*文件系统*/ + +//是否启用文件系统 DmResFile v1版本 +#define MUI_CFG_ENABLE_V1DMRES 1 + +//是否启用 DmResFile v1的硬件加速 需要使用Cryptopp库 +#if (MUI_CFG_ENABLE_V1DMRES) +#define MUI_CFG_ENABLE_CRYPTOPP 0 +#endif + +#if (!MUI_CFG_ENABLE_V1DMRES && !MUI_CFG_ENABLE_V2DMRES) +#error "没有可用的文件系统支持!" +#endif + +/*-------*/ + +/*UI*/ +//是否启用控件默认样式 仅对MUI默认控件有效 +#define MUI_MXML_ENABLE_DEFSTYLE 1 + +//当MXML创建控件时遇到未知控件是否抛出异常 +#define MUI_MXML_THROW_UNKNOWCTRL 0 +/*-------*/ + +/*Debug*/ + +//是否启用调试源信息 +//指定使用M_OutError函数抛出错误时 错误信息是否包含源文件名 +//此选项将导致编译的二进制文件中包含完整的源文件路径字符串信息 这取决于编译器 +#define MUI_CFG_ENABLE_DBGSOURCE 0 + +//指定使用M_OutError函数抛出错误时 错误信息是否包含源函数名 +#define MUI_CFG_ENABLE_DBGFUNNAME 1 + +#endif //__MUI__CONFIG__ \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_Debug.h b/MiaoUI/src/include/Mui_Debug.h new file mode 100644 index 0000000..68f4969 --- /dev/null +++ b/MiaoUI/src/include/Mui_Debug.h @@ -0,0 +1,109 @@ +/** + * FileName: Mui_Debug.h + * Note: 调试输出 Helper + * + * Copyright (C) 2023-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2023-1-19 Create +*/ +#pragma once +#include +#include +#ifdef __ANDROID__ +#include +#endif + +namespace Mui +{ +#ifndef __m_dbg_info__ + +#if (MUI_CFG_ENABLE_DBGSOURCE && MUI_CFG_ENABLE_DBGFUNNAME) + #define __m_dbg_info__ __FILE__, __FUNCTION__ + extern std::wstring _m_dbg_format_(std::string_view __FILE, std::string_view __FUN); +#elif (MUI_CFG_ENABLE_DBGSOURCE || MUI_CFG_ENABLE_DBGFUNNAME) + #if MUI_CFG_ENABLE_DBGSOURCE + #define __m_dbg_info__ __FILE__ + #else + #define __m_dbg_info__ __FUNCTION__ + #endif + extern std::wstring _m_dbg_format_(std::string_view __INFO); +#else + #define __m_dbg_info__ + extern std::wstring _m_dbg_format_(); +#endif +#endif + +#ifndef _M_OutErrorDbg_ +#define _M_OutErrorDbg_(__error, __exit) M_OutError(_m_dbg_format_(__m_dbg_info__), __error, __exit) +#endif + +#ifndef _M_OutMErrorDbg_ +#define _M_OutMErrorDbg_(__error) M_OutError(__error, _m_dbg_format_(__m_dbg_info__)) +#endif + +#ifndef M_ASSERT +#define M_ASSERT(x) if(!(x)) _M_OutErrorDbg_(L"Null pointer reference", true); +#endif + + extern std::exception_ptr _g_last_merror_exception; + extern void _g_last_exception_callback(const MError& ex); + + extern void M_OutError(std::wstring_view cls, std::wstring_view error, bool exit = false); + + extern void M_OutError(const MError& err, std::wstring_view cls = L""); + + template + std::wstring _m_dbg_cast_(T type) + { + std::wostringstream stream; + stream << type; + return stream.str(); + } + + template + void M_DbgOutInfo(T info) + { +#ifdef _WIN32 + OutputDebugStringW(_m_dbg_cast_(info).c_str()); +#elif __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "[Debug]", "%s", M_WStringToString(info).c_str()); +#endif + } + + template + void M_DbgOutInfo(T info, Args... args) + { + M_DbgOutInfo(_m_dbg_cast_(info).c_str()); + M_DbgOutInfo(args...); + } + + template + void MErrorThrow(Args&&... args) + { + auto err = MError(std::forward(args)...); +#ifdef _DEBUG + M_OutError(err); +#endif + _g_last_merror_exception = std::make_exception_ptr(err); + throw std::move(err); + } + + //设定最后的异常通知回调,当std::terminate被调用时 此处是最后能获取异常信息的回调 仅限MError异常 + //空留使用默认的MUI内部回调 会弹窗异常对话框提示 + void M_SetLastExceptionNotify(const std::function& callback = _g_last_exception_callback); + + //在其他线程中抛出异常 并终止程序 + void M_ThreadPostException(std::exception_ptr ex); +} \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_DefUIStyle.h b/MiaoUI/src/include/Mui_DefUIStyle.h new file mode 100644 index 0000000..fb9ed25 --- /dev/null +++ b/MiaoUI/src/include/Mui_DefUIStyle.h @@ -0,0 +1,348 @@ +/** + * FileName: Mui_DefUIStyle.h + * Note: UI默认样式 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-10-1 Create +*/ +#pragma once +#include + +#if MUI_MXML_ENABLE_DEFSTYLE + +namespace Mui::DefStyle +{ + constexpr auto _g_mui_default_uistyle = MXMLCODE( + + + + + + + + + + + + ); + + inline auto _g_mui_default_uistyle_loadxml(UIResourceMgr* mgr) + { + std::wstring xml = MXMLCODE( + + + + + + + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_btn", xml); + + xml = MXMLCODE( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_chbox", xml); + + xml = MXMLCODE( + + + + + + + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_cmlist", xml); + + xml = MXMLCODE( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_cmitem", xml); + + xml = MXMLCODE( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_scroll", xml); + + xml = MXMLCODE( + + + + + + + + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_edit", xml); + + xml = MXMLCODE( + + + + + + + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_list", xml); + + xml = MXMLCODE( + + + + + + + + + + + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_listitem", xml); + + xml = MXMLCODE( + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_strack", xml); + + xml = MXMLCODE( + + + + + + + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_sbutton", xml); + + xml = MXMLCODE( + + + + + + + + + + + + + + + ); + mgr->AddGeometryStyle(L"_mui_def_progress", xml); + } +} + +#endif \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_Error.h b/MiaoUI/src/include/Mui_Error.h new file mode 100644 index 0000000..0fd9bcc --- /dev/null +++ b/MiaoUI/src/include/Mui_Error.h @@ -0,0 +1,95 @@ +/** + * FileName: Mui_Error.h + * Note: 错误信息和类型定义 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-9-18 Create +*/ +#pragma once +#include +#include + +namespace Mui +{ + using _m_error = _m_param; + + enum class MErrorCode : _m_error + { + OK, //正常 + False, //失败 + InvalidParameter, //无效的参数 + NotSupported, //不支持的操作 + InvalidData, //无效的数据 + + DeviceLost = 0x10001, //设备已丢失 需要重建硬件资源 + InternalDeviceError, //设备内部错误 + OutOfMemory, //内存不足 内存分配失败 extCode为分配失败的字节数 + IndexOutOfRange, //索引超出范围 + TheThreadIsBusy, //线程当前正忙 + BufferTooSmall, //缓冲区过小 + + AccessDenied = 0x20001, //拒绝访问 权限不足 + IOError, //I/O错误 extCode可能为系统错误代码 + OutdatedInterface, //已过时的接口 + + RenderInternalError = 0x30001,//渲染器内部错误 + + STDError = 0xA00001, //C++标准库异常 + WinCOMError, //Window COM HRESULT 错误代码 extCode为HRESULT错误代码 + MUIError, //其他MiaoUI错误 使用whatW()获取详细信息 + + Unknown = 0xFFFFFF //未知异常 + }; + + class MError final : std::exception + { + public: + //MiaoUIError + explicit MError(MErrorCode err, _m_error ext = 0) noexcept; + //STDError + explicit MError(const char* what) noexcept; + //COMError + MError(_m_error hresult, std::wstring_view err); + //MUIError + MError(std::wstring_view info); + + //获取错误文本说明(仅英文) 如果错误为COMError则只包括MError的说明文本 com错误的说明文本使用 whatW()获取 + [[nodiscard]] const char* what() const noexcept override; + + //获取宽字符版本的中文+英文文本说明 如果错误为COMError则为HRESULT的对应说明文本 + [[nodiscard]] std::wstring whatW() const; + + //获取错误代码 + [[nodiscard]] MErrorCode errCode() const noexcept; + + //获取扩展代码 如果异常另有说明 则此可以获取附带的信息 + [[nodiscard]] _m_error extCode() const noexcept; + + //获取MErrorCode错误代码具体说明文本(仅英文) + static const char* toMessage(MErrorCode errCode); + + //获取MErrorCode错误代码具体说明文本(中文+英文) + static std::wstring toMessageW(MErrorCode errCode); + + //获取完整的错误信息 错误代码+说明文本 + [[nodiscard]] std::wstring fullText() const; + + private: + MErrorCode m_errCode = MErrorCode::Unknown; + _m_error m_extCode = 0; + std::wstring m_extInfo; + }; +} \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_Framework.h b/MiaoUI/src/include/Mui_Framework.h new file mode 100644 index 0000000..288b294 --- /dev/null +++ b/MiaoUI/src/include/Mui_Framework.h @@ -0,0 +1,42 @@ +/** + * FileName: Mui_Framework.h + * Note: 基础库导入 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-1-24 Create +*/ +#pragma once +//Windows SDK +#ifdef _WIN32 +#include +//#define WIN32_LEAN_AND_MEAN +#include +#endif // _WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_Helper.h b/MiaoUI/src/include/Mui_Helper.h new file mode 100644 index 0000000..d1fa9a0 --- /dev/null +++ b/MiaoUI/src/include/Mui_Helper.h @@ -0,0 +1,248 @@ +/** + * FileName: Mui_Helper.h + * Note: 界面库助手函数 + * + * Copyright (C) 2023-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2023-5-11 Create +*/ +#pragma once +#include + +namespace Mui::Helper +{ + //将2个16位数合成为1个32位数 + inline _m_long M_MAKELONG(_m_word a, _m_word b) + { + return _m_long(_m_ushort(a & 0xffff) | _m_ulong(_m_ushort(b & 0xffff)) << 16); + } + + //高低位 + inline _m_word M_LOWORD(_m_long l) + { + return _m_word(_m_uint(l) & 0xffff); + } + + inline _m_word M_HIWORD(_m_long l) + { + return _m_word(_m_uint(l) >> 16) & 0xffff; + } + + inline UIPoint M_GetMouseEventPt(_m_param lParam) + { + UIPoint pt; + pt.x = static_cast((short)M_LOWORD((_m_long)lParam)); + pt.y = static_cast((short)M_HIWORD((_m_long)lParam)); + return pt; + } + + template + T M_MAX(T a, T b) + { + return a > b ? a : b; + } + + template + T M_MIN(T a, T b) + { + return a < b ? a : b; + } + + template + T M_Clamp(T min, T max, T value) + { + if (value > max) + return max; + if (value < min) + return min; + return value; + } + + //检查小数是否相等 + template + bool M_DecimalEquals(T a, T b, T epsilon = std::numeric_limits::epsilon()) + { + return std::fabs(a - b) < epsilon; + } + + template + constexpr std::array MergeArrayImpl(const std::array& a, const std::array& b, + std::index_sequence, std::index_sequence) + { + return { a[I1]..., b[I2]... }; + } + + template + constexpr std::array MergeArrays(const std::array& a) + { + return a; + } + + //合并数组(std::array) + template + constexpr std::array MergeArrays(const std::array& a, const std::array& b) + { + return MergeArrayImpl(a, b, std::make_index_sequence{}, std::make_index_sequence{}); + } + + //合并数组(std::array) + template + constexpr auto MergeArrays(const std::array& a, const std::array& b, const Rest&... rest) + { + return MergeArrays(MergeArrays(a, b), rest...); + } + + template + constexpr std::array, Size> ToArrayImpl(T(&& Array)[Size], std::index_sequence) + { + return { {std::move(Array[Idx])...} }; + } + + //C++20 std::to_array替代 + template + constexpr std::array, Size> ToArray(T(&& Array)[Size]) + { + return ToArrayImpl(std::move(Array), std::make_index_sequence{}); + } + + //Array转Vector + template + constexpr std::vector ArrayToVector(const std::array& a) + { + std::vector vec(Size); + for (size_t i = 0; i < Size; ++i) + { + vec[i] = a[i]; + } + return vec; + } + + /*计算长宽比 + * @param fromWidth - 原始宽度 + * @param fromHeight - 原始高度 + * @param toWidthOrHeight - 目标宽度或高度 + * + * @return 为true则返回结果为高度,否则为宽度 + */ + extern int M_CalcAspectRatio(int fromWidth, int fromHeight, int toWidthOrHeight, bool isWidth); + + /*-------------字符串操作相关函数-------------*/ + + //wstring 转 string + extern std::string M_WStringToString(std::wstring_view width); + + //string 转 wstring + extern std::wstring M_StringToWString(std::string_view str); + + /*替换字符串 + * @param str - 源字符串 + * @param old_value - 欲替换文本 + * @param new_value - 用作替换的文本 + * + * @return 替换后的文本内容 + */ + extern std::wstring M_ReplaceString(std::wstring str, std::wstring_view old_value, std::wstring_view new_value); + + /*取文本出现次数 + * @param src - 原始字符串 + * @param text - 欲查找的文本 + * + * @return 指定文本在原始字符串中的出现次数 + */ + extern _m_uint M_GetTextCount(std::wstring_view src, std::wstring_view text); + + /*取文本指定行内容 + * @param src - 原始字符串 + * @param line - 欲取的文本行 从1开始 + * + * @return 指定行文本内容 + */ + extern std::wstring M_GetTextLine(std::wstring_view src, _m_uint line); + + //文本转数字 + //@exception MErrorCode::InvalidParameter + extern _m_int M_StoInt(std::wstring_view str); + extern _m_long M_StoLong(std::wstring_view str); + extern _m_long64 M_StoLong64(std::wstring_view str); + extern _m_ulong M_StoULong(std::wstring_view str); + extern _m_ulong64 M_StoULong64(std::wstring_view str); + extern float M_StoFloat(std::wstring_view str); + extern double M_StoDouble(std::wstring_view str); + + /*取属性值 x,x,x,x + * @param value - 值 + * @param out destValue - 输出值 + * @param count - 参数数量 + */ + extern void M_GetAttribValue(std::wstring_view value, std::vector& destValue, _m_uint count); + + /*取属性值Int x,x,x,x + * @param value - 值 + * @param out destValue - 输出值 + * @param count - 参数数量 + * @exception MErrorCode::InvalidParameter + */ + extern void M_GetAttribValueInt(std::wstring_view value, std::vector& destValue, _m_uint count); + + /*取属性值RGBA + * @param value - 颜色值 例如 RGBA、RGB、HEX 255,255,255,255、\@hex:FFFFFFFF 不带\\ 这边是为了注释在IDE正常显示 + * + * @return 颜色值 + * @exception MErrorCode::InvalidParameter + */ + extern _m_color M_GetAttribValueColor(std::wstring_view value); + + //矩形操作相关函数 + namespace Rect + { + //判断矩形是否相交 + extern bool IsCross(const _m_rect& rect1, const _m_rect& rect2); + + //判断点是否在矩形内 + extern bool IsPtInside(const _m_rect& rect, const UIPoint& point); + + /*偏移矩形 + * @param rect - 目标矩形 + * @param dx - X坐标移动 + * @param dy - Y坐标移动 + */ + extern void Offset(_m_rect* rect, int dx, int dy); + + /*扩展矩形 + * @param rect - 目标矩形 + * @param dx - 扩展宽度 + * @param dy - 扩展高度 + */ + extern void Inflate(_m_rect* rect, int dx, int dy); + + /*联合矩形 + * @param dst - 目标矩形 + * @param rect1 - 联合1 + * @param rect2 - 联合2 + */ + extern void Union(_m_rect* dst, const _m_rect* rect1, const _m_rect* rect2); + + /*相交矩形 + * @param dst - 目标矩形 + * @param rect1 - 相交1 + * @param rect2 - 相交2 + */ + extern void Intersect(_m_rect* dst, const _m_rect* rect1, const _m_rect* rect2); + } + + //延时 毫秒级 + extern void M_Sleep(_m_uint ms); + +} \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_RAIIHelper.h b/MiaoUI/src/include/Mui_RAIIHelper.h new file mode 100644 index 0000000..872e0f2 --- /dev/null +++ b/MiaoUI/src/include/Mui_RAIIHelper.h @@ -0,0 +1,243 @@ +/** + * FileName: Mui_RAIIHelper.h + * Note: RAII助手 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-1-24 Create +*/ +#pragma once +#include +#include + +namespace Mui::RAII +{ + template + class lambda_call + { + public: + lambda_call(const lambda_call&) = delete; + lambda_call& operator=(const lambda_call&) = delete; + lambda_call& operator=(lambda_call&& other) = delete; + + explicit lambda_call(TLambda&& lambda) noexcept : m_lambda(std::move(lambda)) + { + static_assert(std::is_same_v, "scope_exit的lambda函数一定不能有返回值"); + static_assert(!std::is_lvalue_reference_v && !std::is_rvalue_reference_v, "scope_exit只能直接与lambda函数一起使用"); + } + + lambda_call(lambda_call&& other) noexcept : m_lambda(std::move(other.m_lambda)), m_call(other.m_call) + { + other.m_call = false; + } + + ~lambda_call() noexcept + { + reset(); + } + //确保scope_exit lambda不会被调用 + void release() noexcept + { + m_call = false; + } + //立刻执行scope_exit lambda如果还没有运行的话;确保它不再次运行 + void reset() noexcept + { + if (m_call) + { + m_call = false; + m_lambda(); + } + } + //返回true如果scope_exit lambda仍要被执行 + [[nodiscard]] explicit operator bool() const noexcept + { + return m_call; + } + protected: + TLambda m_lambda; + bool m_call = true; + }; + + /* + 返回一个对象,该对象在销毁时执行给定的lambda函数,请使用auto捕获返回的对象 + 使用reset()提前执行lambda或使用release()避免执行 + 在lambda中抛出的异常将引发快速失败 + 你可以认为这个对象的作用跟finally差不多 + */ + template + [[nodiscard]] inline auto scope_exit(TLambda&& lambda) noexcept + { + return lambda_call(std::forward(lambda)); + } + + /* Mui资源基本对象 该类型以类似COM的方式进行引用计数释放 + * 一般资源仅用于当前上下文,不可拷贝 不可通用 + * 可以使用Mui_Ptr来自动管理 + */ + class MBasicObj + { + public: + virtual ~MBasicObj() noexcept = default; + MBasicObj(MBasicObj&&) = delete; + MBasicObj(const MBasicObj&) = delete; + MBasicObj& operator=(const MBasicObj&) = delete; + MBasicObj& operator=(MBasicObj&&) = delete; + + void AddRef() noexcept { ++m_ref; } + void Release() + { + if (m_ref > 0) + --m_ref; + if (m_ref != 0) + return; + try + { + ReleaseProc(); + } + catch (...) + { +#ifdef _DEBUG + _M_OutErrorDbg_(L"对象释放发生异常", false); +#endif + } + delete this; + } + + protected: + MBasicObj() noexcept : m_ref(0) {} + virtual void ReleaseProc() {} + + private: + std::atomic_int m_ref; + }; + + template + class Mui_Ptr + { + public: + Mui_Ptr() noexcept : m_ptr(nullptr) {} + + Mui_Ptr(T* other) noexcept : m_ptr(other) + { + if (m_ptr) + m_ptr->AddRef(); + } + + Mui_Ptr(const Mui_Ptr& other) noexcept : m_ptr(other.m_ptr) + { + if (m_ptr) + m_ptr->AddRef(); + } + + Mui_Ptr(Mui_Ptr&& other) noexcept : m_ptr(nullptr) + { + swap(other); + } + + ~Mui_Ptr() noexcept + { + if (m_ptr) + m_ptr->Release(); + } + + Mui_Ptr& operator=(T* other) noexcept + { + if (m_ptr != other) + Mui_Ptr(other).swap(*this); + + return *this; + } + + Mui_Ptr& operator=(const Mui_Ptr& other) noexcept + { + if (this == &other) + return *this; + Mui_Ptr(other).swap(*this); + return *this; + } + + Mui_Ptr& operator=(Mui_Ptr&& other) noexcept + { + Mui_Ptr(std::move(other)).swap(*this); + return *this; + } + + bool operator!=(const Mui_Ptr& other) noexcept + { + return m_ptr != other.m_ptr; + } + + bool operator!=(const T* other) noexcept + { + return m_ptr != other; + } + + bool operator==(const Mui_Ptr& other) noexcept + { + return m_ptr == other.m_ptr; + } + + bool operator==(const T* other) noexcept + { + return m_ptr == other; + } + + [[nodiscard]] T* get() const noexcept + { + return m_ptr; + } + + T** operator&() noexcept + { + return &m_ptr; + } + + T* operator->() const noexcept + { + return m_ptr; + } + + explicit operator bool() const noexcept + { + return m_ptr != nullptr; + } + + void swap(Mui_Ptr& other) noexcept + { + std::swap(m_ptr, other.m_ptr); + } + + template + Mui_Ptr cast() noexcept + { + return reinterpret_cast(m_ptr); + } + + void rest() noexcept + { + operator=(nullptr); + } + + void detach() noexcept + { + m_ptr = nullptr; + } + + using Type = T; + private: + T* m_ptr = nullptr; + }; +} \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_Settings.h b/MiaoUI/src/include/Mui_Settings.h new file mode 100644 index 0000000..a36f55f --- /dev/null +++ b/MiaoUI/src/include/Mui_Settings.h @@ -0,0 +1,70 @@ +/** + * FileName: Mui_Settings.h + * Note: MiaoUI 附加设置声明 + * + * Copyright (C) 2022-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-1-23 Create +*/ +#pragma once +#include + +namespace Mui::Settings +{ + //MiaoUI Logo字符画 + extern const wchar_t* MuiLogoText; + + //MiaoUI 界面库版本 + extern const wchar_t* MuiEngineVer; + + //窗口消息循环 + extern void UIMessageLoop(); + + //扩展flag + enum ExSection + { + //Lite版本不支持任何扩展flag + + //Windows Only + Render_D2D_Factory_Flag_Options, + Render_D2D_D3DCreateDeviceFlag, + Render_D2D_D3DCreateDeviceDriverType, + Render_D2D_RenderNoDXGI, //不使用DXGI渲染表面 使用DC渲染 + //通用 + Graphic_Atlas_Size_Width, //Atlas纹理宽度 默认1024 + Graphic_Atlas_Size_Height, //Atlas纹理高度 默认1024 + Graphic_Atlas_MaxCachePixel, //Atlas最大缓存像素总数 默认2048*2048 + MXML_SVG_Rasterized_Width, //MXMLUI 栅格化SVG图标默认宽度 默认64 + MXML_SVG_Rasterized_Height, //MXMLUI 栅格化SVG图标默认宽度 默认64 + //测试 + Test_DeviceLost + }; + + //ExtensionFlag系函数均为线程安全 但不能递归调用 + + /* 设置扩展flag 这些参数通常只适用于特定平台 + * @param ExSection - 扩展部分 + * @param flag - 附加参数 + * @param override - 是否要求直接覆盖原参数 否则追加flag 这里不是覆盖追加的扩展flag 而是要求实际获取flag的函数怎么使用这些flag + */ + extern void SetExtensionFlag(ExSection section, _m_param flag, bool override); + + //还原默认扩展flag + extern void ResExtensionFlag(ExSection section); + + //获取扩展flag + using ExFlag = std::pair<_m_param, bool>; + extern bool GetExtensionFlag(ExSection section, ExFlag& param); +} \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_TypeDef.h b/MiaoUI/src/include/Mui_TypeDef.h new file mode 100644 index 0000000..cc7ed43 --- /dev/null +++ b/MiaoUI/src/include/Mui_TypeDef.h @@ -0,0 +1,175 @@ +/** + * FileName: Mui_TypeDef.h + * Note: 基本类型定义 + * + * Copyright (C) 2020-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2023-4-1 Create +*/ +#pragma once +#include + +namespace Mui +{ + //数据类型 + using _m_uchar = std::uint8_t; //0 ~ 255 (0xFF) + using _m_short = std::int16_t; //-32768 ~ 32767 + using _m_ushort = std::uint16_t; //0 ~ 65535 (0xFFFF) + using _m_int = std::int32_t; //-2147483648 ~ 2147483647 + using _m_uint = std::uint32_t; //0 ~ 4294967295 (0xFFFFFFFF) + + using _m_param = std::int64_t; //-9223372036854775808 ~ 9223372036854775807 + using _m_ptr = void*; + using _m_ptrv = std::uintptr_t; + + using _m_long = long; + using _m_long64 = _m_param; + using _m_ulong = _m_uint; + using _m_ulong64 = std::uint64_t;//0 ~ 18446744073709552000 (0xFFFFFFFFFFFFFFFF) + + using _m_result = _m_long; + using _m_byte = std::uint8_t; + using _m_lpbyte = _m_byte*; + using _m_word = _m_ushort; + using _m_size = _m_ulong64; + using _m_lpcwstr = const wchar_t*; + using _m_msg = _m_int; + + //默认字体 + constexpr auto M_DEF_SYSTEM_FONTNAME = +#ifdef _WIN32 + L"Microsoft YaHei UI"; +#elif __ANDROID__ + L"Noto Sans CJK TC"; +#else +#error __TODO__ +#endif + + //窗口消息事件代码 + enum MEventCodeEnum + { + M_WND_MOVE = -0x0ffffff, + M_WND_SIZE, + M_WND_SETFOCUS, + M_WND_KILLFOCUS, + M_WND_PAINT, + M_WND_CLOSE, + M_WND_KEYDOWN, + M_WND_KEYUP, + M_WND_COMMAND, + M_WND_SYSCOMMAND, + M_WND_TIMER, + + M_MOUSE_MOVE, + M_MOUSE_WHEEL, + M_MOUSE_HOVER, + M_MOUSE_LEAVE, + M_MOUSE_LBDOWN, + M_MOUSE_LBUP, + M_MOUSE_LBDBCLICK, + M_MOUSE_RBDOWN, + M_MOUSE_RBUP, + M_MOUSE_RBDBCLICK, + M_MOUSE_MBDOWN, + M_MOUSE_MBUP, + M_MOUSE_MBDBCLICK, + + M_SETCURSOR + }; + + //文本对齐方式 + enum TextAlign + { + TextAlign_Left = 0x0000, //居左 + TextAlign_Center = 0x0001, //居中 + TextAlign_Right = 0x0002, //居右 + + TextAlign_Top = 0x0000, //居顶 + TextAlign_VCenter = 0x0004, //垂直居中 + TextAlign_Bottom = 0x0008, //居底 + }; + + //消息事件 + enum UINotifyEvent + { + Event_Mouse_Hover, //鼠标进入 + Event_Mouse_Exited, //鼠标退出 + Event_Mouse_Move, //鼠标移动 + + Event_Mouse_LDown, //鼠标左键按下 + Event_Mouse_LUp, //鼠标左键弹起 + Event_Mouse_LClick, //鼠标左键单击 + Event_Mouse_LDoubleClicked, //鼠标左键双击 + + Event_Mouse_MDown, //鼠标中键按下 + Event_Mouse_MUp, //鼠标中键弹起 + Event_Mouse_MDoubleClicked, //鼠标中键双击 + + Event_Mouse_RDown, //鼠标右键按下 + Event_Mouse_RUp, //鼠标右键弹起 + Event_Mouse_RDoubleClicked, //鼠标右键双击 + + Event_Mouse_Wheel, //鼠标滚轮消息 + + Event_Control_SetCursor, //设置光标 + Event_Control_OnTimer, //计时器消息 + + Event_Focus_True, //获得焦点 + Event_Focus_False, //失去焦点 + + Event_Key_Down, //按下某键 + Event_Key_Up, //按键弹起 + + Event_Edit_TextChanged, //文本变化 + + Event_ListBox_ItemLClick, //表项左键单击 + Event_ListBox_ItemLDBClick, //表项左键双击 + Event_ListBox_ItemChanging, //列表框选项准备更改 + Event_ListBox_ItemChanged, //列表框选项已更改 + + Event_Slider_Change, //滑块值更改 + Event_ColorPicker_Change, //颜色选择器颜色更改 + + Event_NavBar_ItemChange, //导航项目已更改 + + Event_Menu_ItemLClick //菜单项目被左键单击 + }; + + //控件状态 + enum UIControlStatus + { + UIControlStatus_Normal, //普通状态 + UIControlStatus_Hover, //热点状态 + UIControlStatus_Pressed, //按下状态 + UIControlStatus_Disable, //禁止状态 + }; + + //控件定位方式 + enum UIAlignment + { + UIAlignment_Block, //按块方式横向堆叠 如果横向空间不足从下一行开始依次类推 + UIAlignment_LinearV, //按线性方式纵向堆叠 + UIAlignment_LinearVR, //按线性方式纵向堆叠 靠右 + UIAlignment_LinearVB, //按线性方式纵向堆叠 从下向上 靠左 + UIAlignment_LinearVBR, //按线性方式纵向堆叠 从下向上 靠右 + UIAlignment_LinearH, //按线性方式横向堆叠 + UIAlignment_LinearHB, //按线性方式横向堆叠 靠底 + UIAlignment_LinearHL, //按线性方式横向堆叠 从右向左 靠顶 + UIAlignment_LinearHLB, //按线性方式横向堆叠 从右向左 靠底 + UIAlignment_Absolute, //绝对布局 坐标不受布局限制 + UIAlignment_Center, //居中开始 + UIAlignment_Grid //网格布局 暂未实现 + }; +} \ No newline at end of file diff --git a/MiaoUI/src/include/Mui_XML.h b/MiaoUI/src/include/Mui_XML.h new file mode 100644 index 0000000..b525976 --- /dev/null +++ b/MiaoUI/src/include/Mui_XML.h @@ -0,0 +1,256 @@ +/** + * FileName: Mui_XML.h + * Note: 界面创建助手声明 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-12-12 Create +*/ + +#pragma once +#include +#include +#include + +#ifndef MUIEVENT +/*判断UI事件 +* @param event - 事件类型 +* @param name - 控件名 +* @return 是否为此控件触发的事件 +*/ +#define MUIEVENT(_event, _name) (event == (_event) && control->GetName() == (_name)) +#endif +#ifndef MUIEVENTS +/*判断UI事件 +* @param event - 事件类型 +* @param control - 控件指针 +* @return 是否为此控件触发的事件 +*/ +#define MUIEVENTS(_event, _control) (event == (_event) && control == (_control)) +#endif + +#ifndef MXMLCODE +//内联XML代码 +#define MXMLCODE(code) L#code +#endif + +namespace Mui::XML +{ + /*MUI XML UI类 + * 用于从XML创建界面 + * 使用CreateUIFromXML来创建界面 + */ + class MuiXML + { + public: + + UIResourceMgr* Mgr() { return m_window->GetResourceMgr(); } + + /*创建界面自XML文档 + * @param parent - 父控件或Root控件 + * @param xmlDoc - xml文档 + * + * @return 是否创建成功 + * @exception MErrorCode::MUIError + */ + bool CreateUIFromXML(Ctrl::UIControl* parents, std::wstring xmlDoc); + + /*添加字体样式 + * @param name - 样式类名称 + * @param style - 样式 + * + * @return 如果有重复名称 将会失败 + */ + bool AddFontStyle(std::wstring_view name, const Ctrl::UILabel::Attribute& style); + +#if MUI_MXML_ENABLE_DEFSTYLE + //加载默认UI样式 + void LoadDefaultStyle(); +#endif + + /*获取已添加的字体样式 + * 如果未找到 返回默认字体样式 + */ + Ctrl::UILabel::Attribute GetFontStyle(std::wstring_view name); + + /*删除已添加的字体样式 + * @param name - 样式名称 + * + * @return 是否删除成功 如果不存在将返回false + */ + bool DeleteFontStyle(std::wstring_view name); + + /*新建属性组 (PropGroup) + * @param id - 属性组id + * + * @return 如果存在重复属性组返回false + */ + bool CreatePropGroup(std::wstring_view id); + + /*删除属性组 (PropGroup) + * @param id - 属性组id + * + * @return 是否删除成功 如果不存在属性组将返回false + */ + bool DeletePropGroup(std::wstring_view id); + + /*删除属性组属性 (PropGroup) + * @param id - 属性组id + * @param attribName - 属性名 + * + * @return 是否删除成功 如果不存在属性组或属性将返回false + */ + bool DeletePropGroupAttrib(std::wstring_view id, std::wstring_view attribName); + + /*设置属性组属性值 (PropGroup) + * @param id - 属性组id + * @param attribName - 属性名 + * @param attribValue - 属性值 + * + * @return 是否设置成功 如果不存在属性组或属性将返回false + */ + bool SetPropGroupAttrib(std::wstring_view id, std::wstring_view attribName, std::wstring_view attribValue); + + /*添加属性组属性值 (PropGroup) + * @param id - 属性组id + * @param attribName - 属性名 + * @param attribValue - 属性值 + * + * @return 添加设置成功 如果不存在属性组或属性名重复将返回false + */ + bool AddPropGroupAttrib(std::wstring_view id, std::wstring_view attribName, std::wstring_view attribValue); + + /*应用属性组属性 (PropGroup) + * 给指定控件应用指定属性组的所有属性 + * @param dst - 目标控件 + * @param id - 属性组id + * @param draw - 是否更新绘制 + * + * @return 是否应用成功 如果属性组不存在 或控件无效(nullptr)将返回false + */ + bool ApplyPropGroup(Ctrl::UIControl* dst, std::wstring_view id, bool draw = true); + + /*添加默认属性组 (DefProp) + * 默认属性组将绑定XML控件树的所有控件 指定控件默认拥有这些属性 + * @param xml - xml代码 + * @param replace - 如果已经存在默认的属性 是否替换 否则仅追加不存在的属性 + * + * @return 是否添加成功 仅检查xml + */ + bool AddDefPropGroup(std::wstring_view xml, bool replace); + + /*删除默认属性组 + * @param name - 绑定的控件名 + * + * @return 如果指定名称的属性组不存在将返回false + */ + bool DeleteDefPropGroup(std::wstring_view name); + + /*设置默认属性组属性值 + * @param name - 绑定的控件名 + * @param attribList - 要设定的属性列表 如果属性不存在将添加 + * @param draw - 是否更新绘制 + * + * @return 如果指定名称的属性组不存在将返回false + */ + bool SetDefPropGroupAttrib(std::wstring_view name, + const std::vector>& attribList, bool draw = true); + + /*添加字符串列表 + * @param name - 名称 + * @param value - 值 + * + * @return 如果重复将返回false + */ + bool AddStringList(std::wstring_view name, std::wstring_view value); + + /*设置字符串值 + * 所有引用该字符串的控件内容都将被更新 + * @param name - 名称 + * @param value - 值 + * @param draw - 更新关联控件绘制 + * + * @return name无效将返回false + */ + bool SetStringValue(std::wstring_view name, std::wstring_view value, bool draw = true); + + /*删除字符串值 + * 从字符串列表删除字符串 但已引用该字符串的控件不会受到影响 + * @param name - 名称 + * + * @return 是否成功 + */ + bool DeleteStringList(std::wstring_view name); + + /*读取字符串值 + * @param name - 名称 + * + * @return 如果找不到将返回名称 否则为目标字符串值 + */ + std::wstring GetStringValue(std::wstring_view name); + + /*设置控件属性 与控件的SetAttribute不同的是 这里的特殊属性值会被自动转换 和XML中一样 + * 例如 style 原本是指针 在此处则为样式名称 fontStyle、Resource等也一样 value为资源名称 而不再是指针 + * @param control - 要设置的控件 + * @param name - 属性名称 + * @param value - 属性值 + */ + virtual void SetAttribute(Ctrl::UIControl* control, std::wstring_view name, std::wstring value); + + protected: + MuiXML(Window::UIWindowBasic* window); + virtual ~MuiXML(); + + //@exception MErrCode::MUIError + virtual Ctrl::UIControl* CreateControl(std::wstring_view controlName, Ctrl::UIControl* parent); + void SetAttributeInternal(Ctrl::UIControl* control, std::wstring_view name, std::wstring value, bool strlist); + + Ctrl::UILabel::Attribute* FindFontStyle(std::wstring_view name); + + Ctrl::UILabel::Attribute defaultStyle; + std::unordered_map m_fontStyleList; + + struct PropGroup + { + std::unordered_map prop; + std::wstring id; + }; + std::vector m_propList; + std::vector m_defpropList; + + Window::UIWindowBasic* m_window = nullptr; + + struct bindInfo + { + //引用的字符串列表 + struct strdata + { + //first 字符串名 second 属性名 + std::pair str; + int menuIndex = -1;//-1则非菜单引用 否则为菜单索引 + }; + std::vector strList; + }; + std::unordered_map m_bindCtrlList; + + std::unordered_map m_stringList; + +#if MUI_MXML_ENABLE_DEFSTYLE + bool m_defStyleInited = false; +#endif + + friend class Window::UIWindowBasic; + }; +} \ No newline at end of file diff --git a/MiaoUI/src/include/Render/Graphs/Mui_GdipBaseObj.h b/MiaoUI/src/include/Render/Graphs/Mui_GdipBaseObj.h new file mode 100644 index 0000000..a52b2a5 --- /dev/null +++ b/MiaoUI/src/include/Render/Graphs/Mui_GdipBaseObj.h @@ -0,0 +1,251 @@ +/** + * FileName: Mui_GdipBaseObj.h + * Note: GdiPlus 基本对象声明 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-11-6 Create +*/ +#pragma once +#include +#include +#include +#include +#pragma comment(lib, "GdiPlus.lib") + +namespace Mui::Render +{ + using namespace Def; + + //位图 + class MBitmap_GDIP : public MBitmap + { + public: + UISize GetSize() override { return m_size; } + + int GetWidth() override { return m_size.width; } + + int GetHeight() override { return m_size.height; } + + protected: + void ReleaseThis() override; + + UISize m_size; + HBITMAP m_bitmap = nullptr; + HDC m_hdc = nullptr; + + friend class MRender_GDIP; + }; + + //画布 + class MCanvas_GDIP : public MCanvas + { + public: + + UISize GetSize() override { return m_size; } + + int GetWidth() override { return m_size.width; } + + int GetHeight() override { return m_size.height; } + + _m_param GetFlag() override { return 0; } + + _m_rect GetSubRect() override; + + protected: + void ReleaseThis() override; + + UISize m_size; + HBITMAP m_bitmap = nullptr; + HDC m_hdc = nullptr; + + friend class Mui::Window::UIWindowsWnd; + friend class MRender_GDIP; + }; + + //画笔 + class MPen_GDIP : public MPen + { + public: + + void SetColor(_m_color color) override; + + void SetWidth(_m_uint width) override; + + void SetOpacity(_m_byte alpha) override; + + void SetWidthAndColor(_m_uint width, _m_color color) override; + + _m_color GetColor() override; + + _m_uint GetWidth() override; + + _m_byte GetOpacity() override; + + protected: + void ReleaseThis() override; + + _m_color m_colorSrc = 0; + _m_byte m_alpha = 255; + + Gdiplus::Pen* m_pen = nullptr; + + friend class MRender_GDIP; + friend class MFont_GDIP; + }; + + //画刷 + class MBrush_GDIP : public MBrush + { + public: + + void SetColor(_m_color color) override; + + void SetOpacity(_m_byte alpha) override; + + _m_color GetColor() override; + + _m_byte GetOpacity() override; + + protected: + void ReleaseThis() override; + + _m_color m_colorSrc = 0; + _m_byte m_alpha = 255; + + Gdiplus::SolidBrush* m_brush = nullptr; + + friend class MRender_GDIP; + friend class MFont_GDIP; + }; + + class MGradientBrush_GDIP : public MGradientBrush + { + public: + + _m_uint GetColorPosCount() override; + + _m_color GetPosColor(_m_uint index) override; + + void SetOpacity(_m_byte alpha) override; + + _m_byte GetOpacity() override; + + UIPoint GetStartPoint() override; + + void SetStartPoint(UIPoint start) override; + + UIPoint GetEndPoint() override; + + void SetEndPoint(UIPoint end) override; + + protected: + void ReleaseThis() override; + + _m_byte m_alpha = 255; + UIPoint m_start, m_end; + Gdiplus::LinearGradientBrush* m_brush = nullptr; + std::vector> m_vertex; + + friend class MRender_GDIP; + }; + + //字体 + class MFont_GDIP : public MFont + { + public: + + void SetFontName(std::wstring_view name) override; + + void SetFontSize(_m_uint size, std::pair<_m_uint, _m_uint> range) override; + + void SetFontStyle(UIFontStyle style, std::pair<_m_uint, _m_uint> range) override; + + void SetFontColor(MBrush* brush, std::pair<_m_uint, _m_uint> range) override; + + void SetText(std::wstring_view text) override; + + UIRect GetMetrics() override; + + const UIString& GetFontName() override; + + _m_uint GetFontSize() override; + + UIFontStyle GetFontStyle() override; + + _m_color GetFontColor() override; + + const UIString& GetText() override; + + protected: + void ReleaseThis() override; + + void CalcMetrics(); + + void UpdateFont(); + + Gdiplus::Font* m_fontObj = nullptr; + + UIFontStyle m_style; + _m_color m_color = Color::M_Black; + UIString m_text; + UIString m_font; + UIRect m_metrics; + _m_uint m_fontsize = 12; + + friend class MRender_GDIP; + }; + + //图形 + class MGeometry_GDIP : public MGeometry + { + public: + + MGeometryTypes GetGeometryType() override; + + protected: + void ReleaseThis() override; + + MGeometryTypes m_type; + Gdiplus::GraphicsPath* m_geometry = nullptr; + + friend class MRender_GDIP; + }; + + class MRgn_GDIP : public RAII::MBasicObj + { + public: + MRgn_GDIP(int width, int height); + + void Set(HRGN hRgn); + + void ReleaseProc() override; + + UISize GetSize() { return m_size; } + + _m_lpbyte GetBits() { return (_m_lpbyte)m_bits; } + + _m_byte GetBit(int x, int y); + + protected: + //A8 Bitmap + HBITMAP m_bitmap = nullptr; + HDC m_hdc = nullptr; + void* m_bits = nullptr; + UISize m_size; + + friend class MRender_GDIP; + }; +} diff --git a/MiaoUI/src/include/Render/Graphs/Mui_GdipRender.h b/MiaoUI/src/include/Render/Graphs/Mui_GdipRender.h new file mode 100644 index 0000000..7634c8c --- /dev/null +++ b/MiaoUI/src/include/Render/Graphs/Mui_GdipRender.h @@ -0,0 +1,247 @@ +/** + * FileName: Mui_GdipRender.cpp + * Note: GdiPlus 渲染器 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-11-6 Create +*/ +#pragma once +#include + +namespace Mui::Render +{ + struct ALPHAINFO + { + BITMAP bm{}; + LPBYTE lpBuf = nullptr; + RECT rc{}; + }; + class GdiAlpha + { + static BYTE s_byAlphaBack[65536]; + + static LPBYTE ALPHABACKUP(BITMAP* pBitmap, int x, int y, int cx, int cy) + { + LPBYTE lpAlpha = s_byAlphaBack; + if (x + cx >= pBitmap->bmWidth) cx = pBitmap->bmWidth - x; + if (y + cy >= pBitmap->bmHeight) cy = pBitmap->bmHeight - y; + if (cx < 0 || cy < 0 || pBitmap->bmBits == NULL) return NULL; + + if (cx * cy > 65536) lpAlpha = (LPBYTE)malloc(cx * cy); + LPBYTE lpBits = NULL; + for (int iRow = 0; iRow < cy; iRow++) + { + lpBits = (LPBYTE)pBitmap->bmBits + (y + iRow) * pBitmap->bmWidth * 4 + x * 4; + lpBits += 3; + for (int iCol = 0; iCol < cx; iCol++) + { + lpAlpha[iRow * cx + iCol] = *lpBits; + lpBits += 4; + } + } + return lpAlpha; + } + + static void ALPHARESTORE(BITMAP* pBitmap, int x, int y, int cx, int cy, LPBYTE lpAlpha) + { + if (x + cx >= pBitmap->bmWidth) cx = pBitmap->bmWidth - x; + if (y + cy >= pBitmap->bmHeight) cy = pBitmap->bmHeight - y; + if (cx < 0 || cy < 0) return; + LPBYTE lpBits = NULL; + for (int iRow = 0; iRow < cy; iRow++) + { + lpBits = (LPBYTE)pBitmap->bmBits + (y + iRow) * pBitmap->bmWidth * 4 + x * 4; + lpBits += 3; + for (int iCol = 0; iCol < cx; iCol++) + { + *lpBits = lpAlpha[iRow * cx + iCol]; + lpBits += 4; + } + } + if (lpAlpha != s_byAlphaBack) + free(lpAlpha); + } + + public: + static BOOL AlphaBackup(HDC hdc, LPCRECT pRect, ALPHAINFO& alphaInfo) + { + alphaInfo.lpBuf = NULL; + HBITMAP hBmp = (HBITMAP)GetCurrentObject(hdc, OBJ_BITMAP); + GetObject(hBmp, sizeof(BITMAP), &alphaInfo.bm); + + if (alphaInfo.bm.bmBitsPixel != 32) return FALSE; + alphaInfo.rc = *pRect; + alphaInfo.rc.right++; + alphaInfo.rc.bottom++; + POINT pt; + GetViewportOrgEx(hdc, &pt); + RECT rcImg = { 0,0,alphaInfo.bm.bmWidth,alphaInfo.bm.bmHeight }; + OffsetRect(&alphaInfo.rc, pt.x, pt.y); + IntersectRect(&alphaInfo.rc, &alphaInfo.rc, &rcImg); + alphaInfo.lpBuf = ALPHABACKUP(&alphaInfo.bm, alphaInfo.rc.left, alphaInfo.rc.top, alphaInfo.rc.right - alphaInfo.rc.left, alphaInfo.rc.bottom - alphaInfo.rc.top); + return TRUE; + } + + static void AlphaRestore(ALPHAINFO& alphaInfo) + { + if (!alphaInfo.lpBuf) return; + ALPHARESTORE(&alphaInfo.bm, alphaInfo.rc.left, alphaInfo.rc.top, alphaInfo.rc.right - alphaInfo.rc.left, alphaInfo.rc.bottom - alphaInfo.rc.top, alphaInfo.lpBuf); + } + }; + + class MRender_GDIP : public MRender + { + public: + MRender_GDIP() = default; + + _m_lpcwstr GetRenderName() override; + + bool InitRender(_m_uint width, _m_uint height) override; + + bool Resize(_m_uint width, _m_uint height) override; + + MCanvas* CreateCanvas(_m_uint width, _m_uint height, _m_param param) override; + + MBitmap* CreateBitmap(std::wstring_view path, _m_param param = 0U) override; + + MBitmap* CreateBitmap(UIResource resource, _m_param param) override; + + MBitmap* CreateBitmap(_m_uint width, _m_uint height, void* bit, _m_uint len, _m_uint stride) override; + + MBitmap* CreateSVGBitmap(std::wstring_view path, _m_uint width, _m_uint height, bool repColor = false, _m_color color = 0) override; + + MBitmap* CreateSVGBitmapFromXML(std::wstring_view xml, _m_uint width, _m_uint height, bool repColor = false, _m_color color = 0) override; + + MPen* CreatePen(_m_uint width, _m_color color) override; + + MBrush* CreateBrush(_m_color color) override; + + MGradientBrush* CreateGradientBrush(const std::pair<_m_color, float>* vertex, _m_ushort count, UIPoint start, UIPoint end) override; + + MFont* CreateFonts(std::wstring_view text, std::wstring_view fontName, _m_uint fontSize, _m_ptrv fontCollection = 0) override; + + MEffects* CreateEffects(MEffects::Types effect, float value) override; + + MGeometry* CreateRoundGeometry(_m_rect dest, float round) override; + + MGeometry* CreateEllipseGeometry(_m_rect dest) override; + + MCanvas* CreateSubAtlasCanvas(_m_uint width, _m_uint height) override; + + MBatchBitmap* CreateBatchBitmap() override; + + bool CopyBitmapContent(MBitmap* dst, MBitmap* src, UIPoint dstPt, _m_rect srcRect) override; + + bool CopyBitmapContent(MCanvas* dst, MCanvas* src, UIPoint dstPt, _m_rect srcRect) override; + + void BeginDraw() override; + + void SetCanvas(MCanvas* canvas) override; + + void ResetCanvas() override; + + void DrawBitmap(MBitmap* img, _m_byte alpha, _m_rect dest, _m_rect src, bool highQuality) override; + + void DrawBitmap(MCanvas* canvas, _m_byte alpha, _m_rect dest, _m_rect src, bool highQuality) override; + + void DrawBatchBitmap(MBatchBitmap* bmp, MBitmap* input, bool highQuality = true) override; + + void DrawBatchBitmap(MBatchBitmap* bmp, MCanvas* input, bool highQuality = true) override; + + void DrawNinePalacesImg(MBitmap* img, _m_byte alpha, _m_rect dest, _m_rect src, _m_rect margin, bool highQuality) override; + + void DrawNinePalacesImg(MCanvas* canvas, _m_byte alpha, _m_rect dest, _m_rect src, _m_rect margin, bool highQuality) override; + + void DrawRectangle(_m_rect dest, MPen* pen) override; + + void DrawRoundedRect(_m_rect dest, float round, MPen* pen) override; + + void FillRectangle(_m_rect dest, MBrush* brush) override; + + void FillRectangle(_m_rect dest, MGradientBrush* brush) override; + + void FillRoundedRect(_m_rect dest, float round, MBrush* brush) override; + + void DrawTextLayout(MFont* font, _m_rect dest, MBrush* brush, TextAlign alignment) override; + + void DrawBitmapEffects(MBitmap* img, MEffects* effect, _m_byte alpha, _m_rect dest, _m_rect src) override; + + void DrawBitmapEffects(MCanvas* canvas, MEffects* effect, _m_byte alpha, _m_rect dest, _m_rect src) override; + + void DrawLine(UIPoint x, UIPoint y, MPen* pen) override; + + void DrawEllipse(_m_rect dest, MPen* pen) override; + + void FillEllipse(_m_rect dest, MBrush* brush) override; + + void PushClipRect(_m_rect rect) override; + + void PopClipRect() override; + + void PushClipGeometry(MGeometry* geometry) override; + + void PopClipGeometry() override; + + void Clear(_m_color color = 0) override; + + virtual _m_param GetDC(); + + virtual void ReleaseDC(); + + _m_result EndDraw() override; + + MCanvas* GetCanvas() override; + + MCanvas* GetRenderCanvas() override; + + void* Get() override; + + void Flush() override; + + MCanvas* GetSharedCanvas() override; + + bool SaveMBitmap(MBitmap* bitmap, std::wstring_view path, MImgFormat format) override; + + bool SaveMCanvas(MCanvas* canvas, std::wstring_view path, MImgFormat format) override; + + UIResource SaveMBitmap(MBitmap* bitmap, MImgFormat format) override; + + UIResource SaveMCanvas(MCanvas* canvas, MImgFormat format) override; + + bool CheckSubAtlasSource(MCanvas* canvas, MCanvas* canvas1) override; + + //仅32位 位图支持 + static BOOL MAlphaBlend(HDC hdcDest, int xDest, int yDest, int wDest, int hDest, + HDC hdcSrc, int xSrc, int ySrc, int wSrc, int hSrc, BYTE alpha, MRgn_GDIP* mRgn = nullptr); + + MRgn_GDIP* GetMRgn(); + + protected: + void ReleaseThis() override; + + void drawBitmap(HDC hdc, _m_byte alpha, _m_rect dest, _m_rect src, bool highQuality); + UIResource saveBitmap(HBITMAP hbitmap, MImgFormat format); + + std::unique_ptr m_Graphics = nullptr; + + RAII::Mui_Ptr m_Canvas = nullptr; + RAII::Mui_Ptr m_CanvasTmp = nullptr; + RAII::Mui_Ptr m_CanvasDef = nullptr; + RAII::Mui_Ptr m_Rgn = nullptr; + + Gdiplus::GraphicsState m_state = 0; + }; +} \ No newline at end of file diff --git a/MiaoUI/src/include/Render/Graphs/Mui_Render.h b/MiaoUI/src/include/Render/Graphs/Mui_Render.h new file mode 100644 index 0000000..5b50849 --- /dev/null +++ b/MiaoUI/src/include/Render/Graphs/Mui_Render.h @@ -0,0 +1,96 @@ +/** + * FileName: Mui_Render.h + * Note: 渲染功能助手声明 + * + * Copyright (C) 2020-2022 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-17 Create +*/ + +#pragma once +#include + +namespace Mui +{ + namespace Color + { + inline _m_color M_RGB(_m_byte r, _m_byte g, _m_byte b) + { + return { (_m_byte(r) | (_m_word(g) << 8)) | (_m_ulong(b) << 16) }; + } + + inline _m_color M_RGBA(_m_byte r, _m_byte g, _m_byte b, _m_byte a) + { + return M_RGB(r, g, b) | (a << 24); + } + + inline _m_byte M_GetAValue(_m_color rgba) + { + return _m_byte(_m_ulong(rgba >> 24) & 0xff); + } + + inline _m_byte M_GetRValue(_m_color rgb) + { + return _m_byte(_m_ulong(rgb) & 0xff); + } + + inline _m_byte M_GetGValue(_m_color rgb) + { + return _m_byte(_m_ulong(_m_word(rgb) >> 8) & 0xff); + } + + inline _m_byte M_GetBValue(_m_color rgb) + { + return _m_byte(_m_ulong(rgb >> 16) & 0xff); + } + + inline std::wstring M_RGBA_STR(_m_byte r, _m_byte g, _m_byte b, _m_byte a) + { + std::wstring ret = std::to_wstring(r); + ret += L"," + std::to_wstring(g); + ret += L"," + std::to_wstring(b); + ret += L"," + std::to_wstring(a); + return ret; + } + + inline std::wstring M_RGBA_STR(_m_color color) + { + return M_RGBA_STR(M_GetRValue(color), M_GetGValue(color), + M_GetBValue(color), M_GetAValue(color)); + } + + constexpr _m_color M_None = 0x00000000; //RGBA: 0,0,0,0 + constexpr _m_color M_Black = 0xFF000000;//RGBA: 0,0,0,255 + constexpr _m_color M_White = 0xFFFFFFFF;//RGBA: 255,255,255,255 + constexpr _m_color M_RED = 0xFF0000FF; //RGBA: 255,0,0,255 + constexpr _m_color M_GREEN = 0xFF00FF00;//RGBA: 0,255,0,255 + constexpr _m_color M_BLUE = 0xFFFF0000; //RGBA: 0,0,255,255 + } + + template + void MSafeRelease(Interface*& Release) + { + if (Release != nullptr) + { + Release->Release(); + Release = nullptr; + } + } + + namespace Render + { + extern void NinePalaceDraw(const std::function& callback, _m_rect dest, _m_rect src, _m_rect margin); + } +} \ No newline at end of file diff --git a/MiaoUI/src/include/Render/Graphs/Mui_RenderDef.h b/MiaoUI/src/include/Render/Graphs/Mui_RenderDef.h new file mode 100644 index 0000000..4d48a0b --- /dev/null +++ b/MiaoUI/src/include/Render/Graphs/Mui_RenderDef.h @@ -0,0 +1,567 @@ +/** + * FileName: Mui_RenderDef.h + * Note: UI渲染接口定义 + * + * Copyright (C) 2021-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2021-4-18 Create +*/ + +#pragma once +#include + +namespace Mui::Render +{ + class MRenderCmd; + + namespace Def + { + enum class MImgFormat + { + PNG, + JPG, + BMP + }; + + class MRenderObj : public RAII::MBasicObj + { + protected: + void ReleaseProc() final; + virtual void ReleaseThis() = 0; + + bool IsBaseThread(); + bool IsDrawing(); + + void* GetBaseRender(); + + template + T* GetBaseRenderCast() + { + return static_cast(GetBaseRender()); + } + + void* m_base = nullptr; + + friend class Mui::Render::MRenderCmd; + }; + + //位图 + class MBitmap : public MRenderObj + { + public: + + //获取位图尺寸 + virtual UISize GetSize() = 0; + //获取位图宽度 + virtual int GetWidth() = 0; + //获取位图高度 + virtual int GetHeight() = 0; + + }; + + using MBitmapPtr = RAII::Mui_Ptr; + + //画布 + class MCanvas : public MRenderObj + { + public: + + //获取画布尺寸 + virtual UISize GetSize() = 0; + //获取画布宽度 + virtual int GetWidth() = 0; + //获取画布高度 + virtual int GetHeight() = 0; + //获取画布标志 + virtual _m_param GetFlag() = 0; + //获取画布子矩形 + virtual _m_rect GetSubRect() = 0; + + }; + + using MCanvasPtr = RAII::Mui_Ptr; + + //画笔 + class MPen : public MRenderObj + { + public: + + //设置画笔颜色 + virtual void SetColor(_m_color color) = 0; + //设置画笔宽度 + virtual void SetWidth(_m_uint width) = 0; + //设置画笔不透明度 + virtual void SetOpacity(_m_byte alpha) = 0; + //设置宽度和颜色 + virtual void SetWidthAndColor(_m_uint width, _m_color color) = 0; + + //获取颜色 + virtual _m_color GetColor() = 0; + //获取宽度 + virtual _m_uint GetWidth() = 0; + //获取不透明度 + virtual _m_byte GetOpacity() = 0; + + }; + + using MPenPtr = RAII::Mui_Ptr; + + //画刷 + class MBrush : public MRenderObj + { + public: + + //设置画刷颜色 + virtual void SetColor(_m_color color) = 0; + //设置画刷不透明度 + virtual void SetOpacity(_m_byte alpha) = 0; + + //获取颜色 + virtual _m_color GetColor() = 0; + //获取不透明度 + virtual _m_byte GetOpacity() = 0; + + }; + + using MBrushPtr = RAII::Mui_Ptr; + + //线性渐变画刷 + class MGradientBrush : public MRenderObj + { + public: + + //获取画刷颜色顶点数量 + virtual _m_uint GetColorPosCount() = 0; + //获取画刷顶点颜色 + virtual _m_color GetPosColor(_m_uint index) = 0; + + //设置不透明度 + virtual void SetOpacity(_m_byte alpha) = 0; + //获取不透明度 + virtual _m_byte GetOpacity() = 0; + + //获取开始点 + virtual UIPoint GetStartPoint() = 0; + //设置开始点 + virtual void SetStartPoint(UIPoint start) = 0; + + //获取结束点 + virtual UIPoint GetEndPoint() = 0; + //设置结束点 + virtual void SetEndPoint(UIPoint end) = 0; + }; + + using MGradientBrushPtr = RAII::Mui_Ptr; + + //字体 + class MFont : public MRenderObj + { + public: + + //设置字体名称 + virtual void SetFontName(std::wstring_view name) = 0; + //设置字体大小 + virtual void SetFontSize(_m_uint size, std::pair<_m_uint, _m_uint> range) = 0; + //设置字体样式 + virtual void SetFontStyle(UIFontStyle style, std::pair<_m_uint, _m_uint> range) = 0; + //设置字体颜色 + virtual void SetFontColor(MBrush* brush, std::pair<_m_uint, _m_uint> range) = 0; + //设置文本 + virtual void SetText(std::wstring_view text) = 0; + + //测量字体矩形 + virtual UIRect GetMetrics() = 0; + + //获取字体名称 + virtual const UIString& GetFontName() = 0; + //获取字体大小 + virtual _m_uint GetFontSize() = 0; + //获取字体样式 + virtual UIFontStyle GetFontStyle() = 0; + //获取字体颜色 + virtual _m_color GetFontColor() = 0; + //获取文本 + virtual const UIString& GetText() = 0; + + }; + + using MFontPtr = RAII::Mui_Ptr; + + //效果 + class MEffects : public MRenderObj + { + public: + + enum Types + { + GaussianBlur, + BlackAndWhite, + Sepia + }; + + //获取效果名称 + virtual std::wstring GetEffectName() = 0; + + //获取效果类型 + virtual Types GetEffectType() = 0; + + //设置效果参数 + virtual void SetEffectValue(float param) = 0; + + //获取效果参数 + virtual float GetEffectValue() = 0; + + }; + + using MEffectPtr = RAII::Mui_Ptr; + + //图形 + class MGeometry : public MRenderObj + { + public: + + enum MGeometryTypes + { + RoundRect, + Ellipse + }; + + virtual MGeometryTypes GetGeometryType() = 0; + }; + + using MGeometryPtr = RAII::Mui_Ptr; + + //批位图 + class MBatchBitmap : public MRenderObj + { + public: + + //添加子区域 + virtual void AddSub(_m_rect dst, _m_rect src, _m_byte alpha = 255) = 0; + + //删除子区域 + virtual bool DelSub(_m_uint index) = 0; + + //获取子区域数量 + virtual _m_uint GetCount() = 0; + + //清空子区域 + virtual void Clear() = 0; + }; + + using MBatchBitmapPtr = RAII::Mui_Ptr; + + class MRender : public MRenderObj + { + public: + //渲染接口名称 + virtual _m_lpcwstr GetRenderName() = 0; + + //初始化渲染器 + virtual bool InitRender(_m_uint width, _m_uint height) = 0; + + //调整画布大小 + virtual bool Resize(_m_uint width, _m_uint height) = 0; + + /*创建空白画布 + * @param width - 位图宽度 + * @param heigth - 位图高度 + * @param param - 保留flag + * + * @return MCanvas 画布对象指针 + */ + virtual MCanvas* CreateCanvas(_m_uint width, _m_uint height, _m_param param = 0U) = 0; + + /*创建内存位图从内存资源 + * @param resource - 图片资源数据 + * @param param - 保留 + * + * @return MBitmap 内存位图 + */ + virtual MBitmap* CreateBitmap(UIResource resource, _m_param param = 0U) = 0; + + /*创建内存位图从文件 + * @param path - 图片路径 + * @param param - 保留 + * + * @return MBitmap 内存位图 + */ + virtual MBitmap* CreateBitmap(std::wstring_view path, _m_param param = 0U) = 0; + + /*从像素数据创建位图 + * @param width - 图片宽 + * @param height - 图片高 + * @param bit - 像素数据 + * @param len - 像素字节 + * @param stride - 数据步长 + * + * @return MBitmap 内存位图 + */ + virtual MBitmap* CreateBitmap(_m_uint width, _m_uint height, void* bit, _m_uint len, _m_uint stride) = 0; + + /*从SVG文档创建位图 需要启用(MUI_CFG_ENABLE_SVGLOADSUP标志) + * @param path - 文档路径 使用UTF-16编码 + * @param width - 栅格化宽度 0=图像原始宽度 + * @param height - 栅格化高度 0=图像原始高度 + * @param repColor - 是否用新的颜色替换 + * @param color - 如果替换 指定此颜色 + * + * @return MBitmap 内存位图 + */ + virtual MBitmap* CreateSVGBitmap(std::wstring_view path, _m_uint width, _m_uint height, bool repColor = false, _m_color color = 0) = 0; + + /*从SVG文档创建位图 需要启用(MUI_CFG_ENABLE_SVGLOADSUP标志) + * @param xml - XML文档内容 + * @param width - 栅格化宽度 0=图像原始宽度 + * @param height - 栅格化高度 0=图像原始高度 + * @param repColor - 是否用新的颜色替换 + * @param color - 如果替换 指定此颜色 + * + * @return MBitmap 内存位图 + */ + virtual MBitmap* CreateSVGBitmapFromXML(std::wstring_view xml, _m_uint width, _m_uint height, bool repColor = false, _m_color color = 0) = 0; + + /*创建画笔 + * @param width - 画笔宽度 + * @param color - 画笔颜色 + * + * @return MPen 内存画笔 + */ + virtual MPen* CreatePen(_m_uint width, _m_color color) = 0; + + /*创建画刷 + * @param color - 画刷颜色 + * + * @return MBrush 内存画刷 + */ + virtual MBrush* CreateBrush(_m_color color) = 0; + + /*创建线性渐变画刷 + * @param vertex - 顶点数组 + * @param count - 顶点数量 + * @param start - 矩形开始位置 + * @param end - 矩形结束位置 + */ + virtual MGradientBrush* CreateGradientBrush(const std::pair<_m_color, float>* vertex, _m_ushort count, UIPoint start, UIPoint end) = 0; + + /*创建字体 + * @param text - 文本内容 + * @param fontName - 字体名称 + * @param fontSize - 字体大小 + * @param fontCollection - 自定义字体集指针 需确定渲染引擎而定 + * + * @return MFont 内存字体 + */ + virtual MFont* CreateFonts(std::wstring_view text, std::wstring_view fontName, _m_uint fontSize, _m_ptrv fontCollection = 0) = 0; + + /*创建渲染效果 + * @param effect - 欲创建效果类型 + * @param value - 效果附加值w + */ + virtual MEffects* CreateEffects(MEffects::Types effect, float value) = 0; + + //创建圆角矩形图形 + virtual MGeometry* CreateRoundGeometry(_m_rect dest, float round) = 0; + + //创建椭圆图形 + virtual MGeometry* CreateEllipseGeometry(_m_rect dest) = 0; + + //创建子图集画布 + virtual MCanvas* CreateSubAtlasCanvas(_m_uint width, _m_uint height) = 0; + + //创建批位图 + virtual MBatchBitmap* CreateBatchBitmap() = 0; + + //复制位图内容 + virtual bool CopyBitmapContent(MBitmap* dst, MBitmap* src, UIPoint dstPt, _m_rect srcRect) = 0; + + virtual bool CopyBitmapContent(MCanvas* dst, MCanvas* src, UIPoint dstPt, _m_rect srcRect) = 0; + + //开始绘制 + virtual void BeginDraw() = 0; + + //设置当前渲染画布 + virtual void SetCanvas(MCanvas* canvas) = 0; + + //还原默认渲染画布 + virtual void ResetCanvas() = 0; + + /*绘制图像 + * @param Img - 输入图像 + * @param alpha - 绘制透明度 + * @param dest - 绘制区域 + * @param src - 源图区域 + * @param highQuality - 是否高质量绘制模式 + */ + virtual void DrawBitmap(MBitmap* img, _m_byte alpha = 255, _m_rect dest = { 0 }, _m_rect src = { 0 }, bool highQuality = true) = 0; + + virtual void DrawBitmap(MCanvas* canvas, _m_byte alpha = 255, _m_rect dest = { 0 }, _m_rect src = { 0 }, bool highQuality = true) = 0; + + /*绘制批图像 + * @param bmp - 批图像列表 + * @param input - 输入图像 + * @param highQuality - 是否高质量绘制模式 + */ + virtual void DrawBatchBitmap(MBatchBitmap* bmp, MBitmap* input, bool highQuality = true) = 0; + + virtual void DrawBatchBitmap(MBatchBitmap* bmp, MCanvas* input, bool highQuality = true) = 0; + + /*九宫格模式绘制图像 + * @param Img - 输入图像 + * @param alpha - 绘制透明度 + * @param dest - 绘制区域 + * @param src - 源图区域 + * @param margin - 九宫区 + * @param highQuality - 是否高质量绘制模式 + */ + virtual void DrawNinePalacesImg(MBitmap* img, _m_byte alpha, _m_rect dest, _m_rect src, _m_rect margin, bool highQuality = true) = 0; + + virtual void DrawNinePalacesImg(MCanvas* canvas, _m_byte alpha, _m_rect dest, _m_rect src, _m_rect margin, bool highQuality = true) = 0; + + /*绘制矩形边框 + * @param dest - 目标矩形位置 + * @param pen - 绘制用画笔 + */ + virtual void DrawRectangle(_m_rect dest, MPen* pen) = 0; + + /*绘制圆角矩形边框 + * @param dest - 目标矩形位置 + * @param round - 圆角度 + * @param pen - 绘制用画笔 + */ + virtual void DrawRoundedRect(_m_rect dest, float round, MPen* pen) = 0; + + /*填充矩形区域 + * @param dest - 目标矩形位置 + * @param brush - 绘制用画刷 + */ + virtual void FillRectangle(_m_rect dest, MBrush* brush) = 0; + + virtual void FillRectangle(_m_rect dest, MGradientBrush* brush) = 0; + + /*填充圆角矩形区域 + * @param dest - 目标矩形位置 + * @param round - 圆角度 + * @param brush - 绘制用画刷 + */ + virtual void FillRoundedRect(_m_rect dest, float round, MBrush* brush) = 0; + + /*绘制文本布局 + * @param font - 字体 + * @param dest - 目标矩形位置 + * @param brush - 绘制用画刷 + * @param alignment - 文本对齐方式 + */ + virtual void DrawTextLayout(MFont* font, _m_rect dest, MBrush* brush, TextAlign alignment) = 0; + + /*绘制图像效果 + * @param img - 输入图像 + * @param effect - 输入效果 + * @param dest - 绘制区域 + * @param src - 源图区域 + */ + virtual void DrawBitmapEffects(MBitmap* img, MEffects* effect, _m_byte alpha = 255, _m_rect dest = { 0 }, _m_rect src = { 0 }) = 0; + + virtual void DrawBitmapEffects(MCanvas* canvas, MEffects* effect, _m_byte alpha = 255, _m_rect dest = { 0 }, _m_rect src = { 0 }) = 0; + + /*绘制线 + * @param x - 线起始坐标 + * @param y - 线结束坐标 + * @param pen - 绘制用画笔 + */ + virtual void DrawLine(UIPoint x, UIPoint y, MPen* pen) = 0; + + /*绘制椭圆 + * @param dest - 椭圆矩形区域 + * @param pen - 绘制用画笔 + */ + virtual void DrawEllipse(_m_rect dest, MPen* pen) = 0; + + /*填充椭圆 + * @param dest - 椭圆矩形区域 + * @param pen - 绘制用画刷 + */ + virtual void FillEllipse(_m_rect dest, MBrush* brush) = 0; + + /*设置裁剪矩形 + * @param rect - 裁剪区域 + */ + virtual void PushClipRect(_m_rect rect) = 0; + + //恢复裁剪区域 + virtual void PopClipRect() = 0; + + /*设置裁剪图形 + * @param rect - 裁剪图形 + */ + virtual void PushClipGeometry(MGeometry* geometry) = 0; + + //恢复裁剪图形 + virtual void PopClipGeometry() = 0; + + /*清空内容 + * @param color - 用指定颜色填充 默认透明 + */ + virtual void Clear(_m_color color = 0) = 0; + + //结束绘制 + virtual _m_result EndDraw() = 0; + + //取当前画布 + virtual MCanvas* GetCanvas() = 0; + + //取渲染画布 + virtual MCanvas* GetRenderCanvas() = 0; + + //取原始对象 + virtual void* Get() = 0; + + //立即刷新命令队列 + virtual void Flush() = 0; + + //取共享画布 + virtual MCanvas* GetSharedCanvas() = 0; + + /*保存图像到文件 + * @param bitmap - 位图对象 + * @param path - 保存路径 + * @param format - 保存的格式 + * + * @return 是否成功 + */ + virtual bool SaveMBitmap(MBitmap* bitmap, std::wstring_view path, MImgFormat format) = 0; + + virtual bool SaveMCanvas(MCanvas* canvas, std::wstring_view path, MImgFormat format) = 0; + + /*保存图像到内存 + * @param bitmap 位图对象 + * @param format - 保存的格式 + * + * @return 成功返回包含数据 失败返回空的UIResource() + */ + virtual UIResource SaveMBitmap(MBitmap* bitmap, MImgFormat format) = 0; + + virtual UIResource SaveMCanvas(MCanvas* canvas, MImgFormat format) = 0; + + /*检查子图集源 + * @param canvas - 待比较画布 + * @param canvas1 - 待比较画布 + * + * @return 如果两个子图集同属一个Atlas返回true + */ + virtual bool CheckSubAtlasSource(MCanvas* canvas, MCanvas* canvas1) = 0; + }; + } +} diff --git a/MiaoUI/src/include/Render/Mui_RenderMgr.h b/MiaoUI/src/include/Render/Mui_RenderMgr.h new file mode 100644 index 0000000..efcb8a9 --- /dev/null +++ b/MiaoUI/src/include/Render/Mui_RenderMgr.h @@ -0,0 +1,274 @@ +/** + * FileName: Mui_RenderMgr.h + * Note: 渲染命令管理器声明 + * + * Copyright (C) 2021-2022 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2021-12-25 Create +*/ + +#pragma once +#include +#include +#include +#include + +namespace Mui::Render +{ + using namespace Def; + + typedef std::function MRenderProc; + + //环形队列 + template + class MRingQueue + { + public: + MRingQueue(_m_int max) + { + m_maxSize = max; + m_data = new T[max]; + m_front = 0; + m_rear = 0; + } + + ~MRingQueue() + { + if (m_data) + delete[] m_data; + } + + bool isFull() + { + return m_front == (m_rear + 1) % m_maxSize; + } + + bool isEmpty() + { + return m_front == m_rear; + } + + _m_uint size() + { + if (m_rear >= m_front) + return m_rear - m_front; + return m_maxSize - (m_front - m_rear); + } + + _m_uint maxSize() + { + return m_maxSize; + } + + bool push(const T& p) + { + if (!isFull()) + { + m_data[m_rear] = p; + m_rear = (m_rear + 1) % m_maxSize; + return true; + } + return false; + } + + bool push(T&& p) + { + if (!isFull()) + { + m_data[m_rear] = std::move(p); + m_rear = (m_rear + 1) % m_maxSize; + return true; + } + return false; + } + + bool pop(T& p) + { + if (!isEmpty()) + { + p = m_data[m_front]; + m_front = (m_front + 1) % m_maxSize; + return true; + } + return false; + } + + private: + T* m_data = nullptr; + _m_int m_maxSize = 0; + std::atomic_int m_front; + std::atomic_int m_rear; + }; + + /*渲染命令队列 + * 用于处理渲染命令的线程同步 + */ + class MRenderCmd + { + public: + MRenderCmd(MRender* base); + ~MRenderCmd(); + + _m_lpcwstr GetRenderName(); + + bool InitRender(_m_uint width, _m_uint height); + + bool Resize(_m_uint width, _m_uint height); + + MCanvasPtr CreateCanvas(_m_uint width, _m_uint height, _m_param param = 0U); + + MBitmapPtr CreateBitmap(std::wstring_view path, _m_param param = 0U); + + MBitmapPtr CreateBitmap(UIResource resource, _m_param param = 0U); + + MBitmapPtr CreateBitmap(_m_uint width, _m_uint height, void* bit, _m_uint len, _m_uint stride); + + MBitmapPtr CreateSVGBitmap(std::wstring_view path, _m_uint width, _m_uint height, bool repColor = false, _m_color color = 0); + + MBitmapPtr CreateSVGBitmapFromXML(std::wstring_view xml, _m_uint width, _m_uint height, bool repColor = false, _m_color color = 0); + + MPenPtr CreatePen(_m_uint width, _m_color color); + + MBrushPtr CreateBrush(_m_color color); + + MGradientBrushPtr CreateGradientBrush(const std::pair<_m_color, float>* vertex, _m_ushort count, UIPoint start, UIPoint end); + + MFontPtr CreateFonts(std::wstring_view text, std::wstring_view fontName, _m_uint fontSize, _m_ptrv fontCollection = 0); + + MEffectPtr CreateEffects(MEffects::Types effect, float value); + + MGeometryPtr CreateRoundGeometry(_m_rect dest, float round); + + MGeometryPtr CreateEllipseGeometry(_m_rect dest); + + MCanvasPtr CreateSubAtlasCanvas(_m_uint width, _m_uint height); + + MBatchBitmapPtr CreateBatchBitmap(); + + bool CopyBitmapContent(MBitmap* dst, MBitmap* src, UIPoint dstPt, _m_rect srcRect); + + bool CopyBitmapContent(MCanvas* dst, MCanvas* src, UIPoint dstPt, _m_rect srcRect); + + void BeginDraw(); + + void SetCanvas(MCanvasPtr canvas); + + void ResetCanvas(); + + void DrawBitmap(MBitmapPtr img, _m_byte alpha = 255, _m_rect dest = { 0,0,0,0 }, _m_rect src = { 0,0,0,0 }, bool highQuality = true); + + void DrawBitmap(MCanvasPtr canvas, _m_byte alpha = 255, _m_rect dest = { 0,0,0,0 }, _m_rect src = { 0,0,0,0 }, bool highQuality = true); + + void DrawBatchBitmap(MBatchBitmapPtr bmp, MBitmapPtr input, bool highQuality = true); + + void DrawBatchBitmap(MBatchBitmapPtr bmp, MCanvasPtr input, bool highQuality = true); + + void DrawNinePalacesImg(MBitmapPtr img, _m_byte alpha, _m_rect dest, _m_rect src, _m_rect margin, bool highQuality = true); + + void DrawNinePalacesImg(MCanvasPtr canvas, _m_byte alpha, _m_rect dest, _m_rect src, _m_rect margin, bool highQuality = true); + + void DrawRectangle(_m_rect dest, MPenPtr pen); + + void DrawRoundedRect(_m_rect dest, float round, MPenPtr pen); + + void FillRectangle(_m_rect dest, MBrushPtr brush); + + void FillRectangle(_m_rect dest, MGradientBrushPtr brush); + + void FillRoundedRect(_m_rect dest, float round, MBrushPtr brush); + + void DrawTextLayout(MFontPtr font, _m_rect dest, MBrushPtr brush, TextAlign alignment); + + void DrawBitmapEffects(MBitmapPtr img, MEffectPtr effect, _m_byte alpha = 255, _m_rect dest = { 0,0,0,0 }, _m_rect src = { 0,0,0,0 }); + + void DrawBitmapEffects(MCanvasPtr canvas, MEffectPtr effect, _m_byte alpha = 255, _m_rect dest = { 0,0,0,0 }, _m_rect src = { 0,0,0,0 }); + + void DrawLine(UIPoint x, UIPoint y, MPenPtr pen); + + void DrawEllipse(_m_rect dest, MPenPtr pen); + + void FillEllipse(_m_rect dest, MBrushPtr brush); + + void PushClipRect(_m_rect rect); + + void PopClipRect(); + + void PushClipGeometry(MGeometryPtr geometry); + + void PopClipGeometry(); + + void Clear(_m_color color = 0); + + _m_result EndDraw(); + + MCanvas* GetCanvas(); + + MCanvas* GetRenderCanvas(); + + void* Get(); + + void Flush(); + + MCanvas* GetSharedCanvas(); + + bool SaveMBitmap(MBitmapPtr bitmap, std::wstring path, MImgFormat format); + + bool SaveMCanvas(MCanvasPtr canvas, std::wstring path, MImgFormat format); + + UIResource SaveMBitmap(MBitmapPtr bitmap, MImgFormat format); + + UIResource SaveMCanvas(MCanvasPtr canvas, MImgFormat format); + + bool CheckSubAtlasSource(MCanvasPtr canvas, MCanvasPtr canvas1); + + void RunTask(std::function&& task) + { + Task(std::move(task)); + } + + template + T* GetBase() const + { + return dynamic_cast(m_base); + } + + bool IsTaskThread(); + + private: + void ThreadProc(); + void Task(std::function&& task); + + MRender* m_base = nullptr; + + //线程相关 + std::mutex m_tasklock; + std::condition_variable m_signal; + std::atomic_bool m_stop; + std::thread m_thread; + + struct taskParam + { + std::function task = nullptr; + std::promise& notification; + taskParam(std::function& _task, std::promise& n) : + task(_task), notification(n) {} + }; + std::vector m_taskList; + + std::atomic_bool m_begindraw; + + friend class MRenderObj; + }; +} diff --git a/MiaoUI/src/include/Render/Node/Mui_Layout.h b/MiaoUI/src/include/Render/Node/Mui_Layout.h new file mode 100644 index 0000000..210ac0b --- /dev/null +++ b/MiaoUI/src/include/Render/Node/Mui_Layout.h @@ -0,0 +1,76 @@ +/** + * FileName: Mui_Layout.h + * Note: UI布局器 + * + * Copyright (C) 2020-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2023-4-2 Create +*/ +#pragma once +#include + +namespace Mui +{ + namespace Render { class UINodeBase; } + + class UILayouter + { + public: + UILayouter() = default; + virtual ~UILayouter() = default; + void Layout(Render::UINodeBase*, size_t = 0, bool = true); + + void SetType(UIAlignment align); + + [[nodiscard]] UIAlignment GetType() const { return m_type; } + + protected: + UIAlignment m_type = UIAlignment_Block; + bool m_linearParam_vertical = true; + bool m_linearParam_filp = false; + bool m_linearParam_swap = false; + + void Intersect(_m_rectf* dst, const _m_rectf* rect1, const _m_rectf* rect2); + + //查找目标Node指定Index相邻且有效的上一个Node 找不到返回nullptr + Render::UINodeBase* GetPreviousNode(Render::UINodeBase*, size_t); + + //计算Frame内容尺寸 + _m_sizef CalcContentSize(Render::UINodeBase*, bool); + + _m_sizef SizeClamp(Render::UINodeBase*, _m_sizef, _m_scale scale); + + //获取计算内边距后的Frame和ClipFrame + std::pair<_m_rectf, _m_rectf> CalcPadding(Render::UINodeBase*); + + //基本Frame计算 int 0=默认 1=翻转x 2=翻转y 3=翻转xy + _m_rectf& CalcBaseFrame(Render::UINodeBase*, const _m_rectf&, _m_pointf&, _m_pointf, bool, int = 0); + + void LayoutBlock(Render::UINodeBase*, size_t, bool); + + //param: vertical(bool) + void LayoutLinear(Render::UINodeBase*, size_t, bool); + + void LayoutAbsolute(Render::UINodeBase*, size_t, bool); + + void LayoutCenter(Render::UINodeBase*, bool); + + void LayoutGrid(Render::UINodeBase*); + + void AnimationLayout(Render::UINodeBase*); + + friend class Render::UINodeBase; + }; +} \ No newline at end of file diff --git a/MiaoUI/src/include/Render/Node/Mui_RenderNode.h b/MiaoUI/src/include/Render/Node/Mui_RenderNode.h new file mode 100644 index 0000000..8b16e7a --- /dev/null +++ b/MiaoUI/src/include/Render/Node/Mui_RenderNode.h @@ -0,0 +1,121 @@ +/** + * FileName: Mui_RenderNode.h + * Note: 渲染Node声明 + * + * Copyright (C) 2022-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-11-23 Create +*/ +#pragma once +#include + +namespace Mui::Render +{ + class MNodeRoot; + + class MRenderNode + { + public: + virtual ~MRenderNode(); + protected: + MRenderNode() = default; + MRenderNode(MRenderNode* parent); + + //设置和获取Node可见性 + void Visible(bool visible); + [[nodiscard]] bool Visible() const; + + //设置和获取Name + void Name(const UIString& name); + [[nodiscard]] UIString Name() const; + + //添加子Node + void AddChildNode(MRenderNode* node); + //移除子Node + bool DelChildNode(MRenderNode* node); + bool DelChildNode(const UIString& name); + //查找子Node + [[nodiscard]] MRenderNode* FindChildNode(const UIString& name) const; + + //设置父和Node nullptr则从父Node移除 + void Parent(MRenderNode* node); + [[nodiscard]] MRenderNode* Parent() const; + + virtual void OnRender(MRenderCmd* render, void* data) = 0; + virtual void OnRenderChildEnd(MRenderCmd* render, void* data) = 0; + + MRenderNode(MNodeRoot* root); + void DelChildNode(std::vector::iterator& iter); + + auto& GetNodeList() { return m_nodeList; } + + MRenderCmd* m_render = nullptr; + + private: + bool m_visible = true; + bool m_parentVisible = true; + + MRenderNode* m_parent = nullptr; + MNodeRoot* m_root = nullptr; + + UIString m_name; + + std::vector m_nodeList; + + std::recursive_mutex m_lock; + + friend class MNodeRoot; + }; + + class MNodeRoot + { + public: + MNodeRoot(MRenderNode* root); + virtual ~MNodeRoot(); + [[nodiscard]] MRenderNode* RootNode() const; + + void RenderTree(void* data); + + [[nodiscard]] size_t GetCount() const + { + return m_drawList.size(); + } + + //设定回调后UnbindNodeRenderFunc时将会调用 通知控件树发生已变动 + void SetUnBindCallback(std::function&& callback) + { + m_callback = std::move(callback); + } + + private: + std::mutex mx; + + //Node绘制列表 + struct drawNode + { + MRenderNode* node = nullptr; + }; + std::vector m_drawList; + MRenderNode* m_rootNode = nullptr; + MRenderCmd* m_render = nullptr; + + std::function m_callback = nullptr; + + void BindNodeRenderFunc(MRenderNode* last, MRenderNode* node); + void UnbindNodeRenderFunc(MRenderNode* node); + + friend class MRenderNode; + }; +} diff --git a/MiaoUI/src/include/Render/Node/Mui_UINodeBase.h b/MiaoUI/src/include/Render/Node/Mui_UINodeBase.h new file mode 100644 index 0000000..5350d3f --- /dev/null +++ b/MiaoUI/src/include/Render/Node/Mui_UINodeBase.h @@ -0,0 +1,376 @@ +/** + * FileName: Mui_UINodeBase.h + * Note: UINodeBase声明 + * + * Copyright (C) 2022-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-11-27 Create +*/ +#pragma once +#include +#include +#include + +namespace Mui::Render +{ + enum class UICacheType + { + Auto, + Enable, + Disable + }; + + struct UIBkgndStyle + { + _m_color bkgndColor = 0; + _m_color FrameColor = 0; + _m_ushort FrameWidth = 0; + float RoundValue = 0; + _m_color ShadowColor = 0; //Lite版本无效 + UIPoint ShadowOffset; //Lite版本无效 + int ShadowExtend = 0; //Lite版本无效 + float ShadowRadius = 6.f; //Lite版本无效 + }; + + class UINodeBase : protected MRenderNode + { + public: + UINodeBase(); + ~UINodeBase() override; + + enum PosSizeUnitType + { + Default, //默认 + Pixel, //像素 + Percentage, //百分比 + FillMinus //填充减少 + }; + struct PosSizeUnit + { + PosSizeUnitType x_w = Pixel; + PosSizeUnitType y_h = Pixel; + }; + + //设置名称 + virtual void SetName(const UIString& name); + + //获取名称 + [[nodiscard]] virtual UIString GetName() const; + + /*设置位置 + * @param point - 位置 + * @param draw - 刷新绘制(默认true) + */ + virtual void SetPos(UIPoint position, bool draw = true); + + /*设置边框位置 + * @param x - left + * @param y - top + * @param draw - 刷新绘制(默认true) + */ + virtual void SetPos(int x, int y, bool draw = true); + + //获取控件位置 + [[nodiscard]] virtual UIPoint GetPos() const; + + //设置pos单位 + virtual void SetPosUnit(PosSizeUnit unit, bool draw = true); + + //获取pos单位 + [[nodiscard]] virtual PosSizeUnit GetPosUnit() const; + + /*设置尺寸 + * @param size - 尺寸 + * @param draw - 刷新绘制(默认true) + */ + virtual void SetSize(UISize size, bool draw = true); + + /*设置尺寸 + * @param width - 宽度 + * @param height - 高度 + * @param draw - 刷新绘制(默认true) + */ + virtual void SetSize(int width, int height, bool draw = true); + + //获取尺寸 + [[nodiscard]] virtual UISize GetSize() const; + + //设置尺寸单位 + virtual void SetSizeUnit(PosSizeUnit unit, bool draw = true); + + //获取尺寸单位 + [[nodiscard]] virtual PosSizeUnit GetSizeUnit() const; + + /*设置最小尺寸限制 + * @param size - 尺寸 + * @param draw - 刷新绘制(默认true) + */ + virtual void SetMinSize(UISize size, bool draw = true); + + //获取最小尺寸限制 + [[nodiscard]] virtual UISize GetMinSize() const; + + /*设置最大尺寸限制 + * @param size - 尺寸 + * @param draw - 刷新绘制(默认true) + */ + virtual void SetMaxSize(UISize size, bool draw = true); + + //获取最大尺寸限制 + [[nodiscard]] virtual UISize GetMaxSize() const; + + /*设置内边距 + * @param padding - 内边距 + * @param draw - 刷新绘制(默认true) + */ + virtual void SetPadding(UIRect padding, bool draw = true); + + //获取内边距 + [[nodiscard]] virtual UIRect GetPadding() const; + + /*设置定位类型 + * @param type - 定位方式 + * @param draw - 刷新绘制(默认true) + */ + virtual void SetAlignType(UIAlignment type, bool draw = true); + + //获取定位类型 + [[nodiscard]] virtual UIAlignment GetAlignType() const; + + /*设置可见性 + * @param visible - 可视 + * @param draw - 立即绘制(默认true) + */ + virtual void SetVisible(bool visible, bool draw = true); + + //是否可见 + [[nodiscard]] virtual bool IsVisible() const; + + /*设置不透明度 + * @param alpha - 不透明度(0-255) + * @param draw - 立即绘制(默认true) + */ + virtual void SetAlpha(_m_byte alpha, bool draw = true); + + //获取不透明度 + [[nodiscard]] virtual _m_byte GetAlpha() const; + + //设置父控件 如果为null则将自身从父控件中移除 + virtual void SetParent(UINodeBase* UINode); + + //获取控件父级 + [[nodiscard]] virtual UINodeBase* GetParent() const; + + //添加子Node + virtual void AddChildren(UINodeBase* UINode); + + //删除子Node + virtual void RemoveChildren(UINodeBase* UINode); + virtual void RemoveChildren(const UIString& name); + + //查找子Node + [[nodiscard]] virtual UINodeBase* FindChildren(const UIString& name) const; + + //获取子Node数量 + [[nodiscard]] virtual _m_uint GetChildrenCount(); + + //获取子Node列表 + virtual void GetChildrenList(std::vector& list); + + //更新显示 + virtual void UpdateDisplay(bool updateCache = false); + + //更新布局显示 + virtual void UpdateLayout(); + + //缓存支持 + virtual void SetCacheType(UICacheType type); + + //是否启用缓存 + [[nodiscard]] virtual UICacheType GetCacheType() const; + + /*设置动画状态 Lite版本无效 + * @param begin - 动画是准备开始还是已经结束 + */ + virtual void SetAnimationState(bool begin); + + //设置背景样式 + virtual void SetBackground(UIBkgndStyle style); + + //获取背景样式 + [[nodiscard]] virtual UIBkgndStyle GetBkgndStyle() const; + + //设置Frame缩放比 + virtual void SetScale(_m_rcscale scale, bool draw = true); + + //获取Frame缩放比 + [[nodiscard]] virtual _m_rcscale GetScale() const; + + //是否启用DPI缩放 + virtual void EnableDPIScale(bool enable); + + [[nodiscard]] virtual bool IsDPIScaleEnabled() const; + + //设置Frame 仅HideNode = true时有效 + void Frame(_m_rect frame); + + //获取控件Frame矩形 + [[nodiscard]] _m_rect Frame() const; + + //隐藏当前Node 不参与计算 GetChildrenList也不包括 但不影响Find + void HideNode(bool hide, bool draw = true); + + [[nodiscard]] bool HideNode() const; + + //设置是否自动计算尺寸 + void AutoSize(bool autosize, bool draw = true); + + //获取AutoSize + [[nodiscard]] bool AutoSize() const; + + //手动初始化资源 如果资源已初始化则无效否则初始化资源 + void InitDeviceResource(); + + protected: + + /*加载设备资源 + * 该函数至少会被调用一次 用以初始化设备资源 + * 如果为true则应当立即释放并重建MRender所创建的设备资源 + */ + virtual void OnLoadResource(MRenderCmd* render, bool recreate); + + //获取有效的父Node 找不到返回nullptr + [[nodiscard]] UINodeBase* GetValidParent() const; + + //已计算DPI + _m_rcscale GetRectScale() const + { + _m_rcscale scale = m_data.RectScale; + if (m_data.EnableDPIScale) + { + scale.xs = m_data.DPIScale.cx * scale.xs; + scale.ys = m_data.DPIScale.cy * scale.ys; + scale.ws = m_data.DPIScale.cx * scale.ws; + scale.hs = m_data.DPIScale.cy * scale.hs; + } + return scale; + } + + template + void SizeScale(T& dstX, T& dstY, bool dpi = true) + { + _m_scale scale = m_data.RectScale.scale(); + if(dpi && m_data.EnableDPIScale) + { + scale.cx *= m_data.DPIScale.cx; + scale.cy *= m_data.DPIScale.cy; + } + dstX = _scale_to(dstX, scale.cx); + dstY = _scale_to(dstY, scale.cy); + } + + template + void SizeScale(T& dst, bool x, bool dpi = true) + { + _m_scale scale = m_data.RectScale.scale(); + if (dpi && m_data.EnableDPIScale) + { + if(x) scale.cx *= m_data.DPIScale.cx; + else scale.cy *= m_data.DPIScale.cy; + } + dst = _scale_to(dst, x ? scale.cx : scale.cy); + } + + void UpdateScale(); + virtual void OnScale(_m_scale scale); + + //获取控件尺寸 已计算DPI和Scale 在计算布局时被调用 + virtual _m_sizef GetContentSize(); + //获取已计算的Point值 + virtual _m_pointf GetCalcedPoint(); + //布局计算完成后调用 + virtual void OnLayoutCalced() {} + + //绘制 + void OnRender(MRenderCmd* render, void* data) override; + void OnRenderChildEnd(MRenderCmd* render, void* data) override; + + struct MPaintParam + { + MRenderCmd* render = nullptr; + MPCRect clipRect = nullptr; + MPCRect destRect = nullptr; + bool cacheCanvas = false; + _m_byte blendedAlpha = 255; + }; + using MPCPaintParam = const MPaintParam*; + virtual void OnPaintProc(MPCPaintParam param) { } + + void PaintBackground(MRenderCmd* render, MPCRect dst, bool cache); + public: + struct data + { + UISize Size; //Node尺寸 + PosSizeUnit SizeUnit; //Node尺寸单位 + UIPoint Position; //Node位置 + PosSizeUnit PosUnit; //Node位置单位 + + UISize MinSize; //最小尺寸 + UISize MaxSize = { -1, -1 };//最大尺寸 + + Window::UIWindowBasic* ParentWnd = nullptr;//父窗口 + + _m_rectf Frame; //Node矩形 + _m_rectf ClipFrame; //clip矩形 + _m_rect Padding; //内边距 + UILayouter Align; //UI布局器 + + _m_byte AlphaSrc = 255; //原始不透明度值 + _m_byte AlphaDst = 255; //当前不透明度值 + + UICacheType CacheType = UICacheType::Auto;//缓存类型 Lite版本无效 + + MCanvasPtr SubAtlas = nullptr;//子图集画布 + bool HideThis = false; //隐藏当前Node + bool AutoSize = false; //自动计算Size + bool EnableDPIScale = true; //启用DPI缩放 + + _m_scale DPIScale = { 1.f,1.f }; + _m_rcscale RectScale = { 1.f,1.f,1.f,1.f }; + }; + + protected: + + data m_data; + + struct brush + { + MPenPtr FramePen = nullptr; //边框画笔 + MBrushPtr BkgndBrush = nullptr;//背景画刷 + } m_brush; + + UIBkgndStyle m_bgStyle; + + //Lite版本无效 + bool m_cacheSupport = false; + bool m_cacheUpdate = true; + + std::atomic_bool m_initialized; + + friend class Mui::UILayouter; + friend class Window::UIWindowBasic; + }; + +} diff --git a/MiaoUI/src/include/User/Mui_Engine.h b/MiaoUI/src/include/User/Mui_Engine.h new file mode 100644 index 0000000..709ef8a --- /dev/null +++ b/MiaoUI/src/include/User/Mui_Engine.h @@ -0,0 +1,143 @@ +/** + * FileName: Mui_Engine.h + * Note: 引擎主类封装声明 + * + * Copyright (C) 2022-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-12-28 Create +*/ +#pragma once +#include +#include + +namespace Mui +{ + class MWindowCtx; + + class MiaoUI final + { + public: + MiaoUI(MiaoUI&&) = delete; + MiaoUI(const MiaoUI&) = delete; + MiaoUI& operator=(const MiaoUI&) = delete; + MiaoUI& operator=(MiaoUI&&) = delete; + MiaoUI() = default; + ~MiaoUI(); + + enum class Render + { + Auto, //根据平台自动选择 + Direct2D_3D, //仅Windows可用 Lite版本不可用 + OpenGL_Core, //仅桌面端平台可用 Lite版本不可用 + OpenGL_ES, //仅Android可用 Lite版本不可用 + Custom, //自定义渲染器 + + Gdiplus //仅Windows可用 仅Lite版本可用 + }; + + enum class MWindowType + { + Normal, //普通默认系统窗口 + NoTitleBar, //无标题栏窗口 带系统边框和动画 + Popup, //弹出式无边框窗口 + Layer, //分层窗口 + }; + + /*初始化 + * @param error - 输出错误信息 + * @param render - 渲染器类型 + * @param param - 渲染器附加参数 + * @return bool 如果失败返回false并将原因写入error文本 + */ + bool InitEngine(std::wstring& error, Render render = Render::Auto, _m_ptrv param = 0); + + /*反初始化 + * 释放所用的资源和所有窗口 + */ + void UnInitEngine(); + + //添加资源文件 + bool AddResourcePath(std::wstring_view path, std::wstring_view key); + bool AddResourceMem(UIResource memfile, std::wstring_view key); + + + //移除资源文件引用 仅路径添加的有效 + bool RemoveResource(std::wstring_view path); + + /*创建窗口上下文 + * @param rect - 窗口矩形 + * @param type - 窗口类型 + * @param title - 窗口标题 + * @param visible - 是否可视 + * @param main - 是否为主窗口 + * @param dpi - 启用dpi自动缩放 + * @param parent - 父窗口 + * @param exparam - 扩展参数 + * + * @return 窗口上下文指针 + */ + MWindowCtx* CreateWindowCtx(UIRect rect, MWindowType type, std::wstring_view title, + bool main, bool dpi = true, _m_param parent = 0, _m_ptrv exparam = 0); + + private: + std::vector m_windowList{}; + std::unordered_map m_res_pathList{}; + std::vector> m_res_memList{}; + + bool m_isinit = false; + Render m_renderType = Render::Auto; + _m_ptrv m_customRender = 0; + + Mui::Render::MRender* CreateRender(); + + friend class MWindowCtx; + }; + + using MWndEventCallback = std::function; + using MWndDefEventSource = std::function<_m_result(MEventCodeEnum, _m_param)>; + using MWndSourceCallback = std::function<_m_result(MWindowCtx*, MWndDefEventSource, MEventCodeEnum, _m_param)>; + using MWndInitUICallback = std::function; + + class MWindowCtx + { + public: + MWindowCtx(MWindowCtx&&) = delete; + MWindowCtx(const MWindowCtx&) = delete; + MWindowCtx& operator=(const MWindowCtx&) = delete; + MWindowCtx& operator=(MWindowCtx&&) = delete; + virtual ~MWindowCtx(); + + [[nodiscard]] Window::UIWindowBasic* Base() const; + + [[nodiscard]] XML::MuiXML* XML() const; + + bool InitWindow(const MWndInitUICallback& callback, bool visible); + + void SetEventCallback(MWndEventCallback callback); + + void SetEventSourceCallback(MWndSourceCallback callback); + + void EventLoop(); + + protected: + MWindowCtx(); + Window::UIWindowBasic* m_base = nullptr; + MiaoUI* m_engine = nullptr; + + bool m_isInit = false; + + friend class MiaoUI; + }; +} \ No newline at end of file diff --git a/MiaoUI/src/include/Window/Mui_BasicWnd.h b/MiaoUI/src/include/Window/Mui_BasicWnd.h new file mode 100644 index 0000000..6554d70 --- /dev/null +++ b/MiaoUI/src/include/Window/Mui_BasicWnd.h @@ -0,0 +1,331 @@ +/** + * FileName: Mui_BasicWnd.h + * Note: UI窗口容器声明 + * + * Copyright (C) 2021-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2021-4-28 Create +*/ +#pragma once +#include +#include +#include + +namespace Mui +{ + namespace XML { class MuiXML; } + namespace Render { class UINodeBase; } + + class MiaoUI; + class MWindowCtx; + + namespace Window::IWindow + { + class UINativeWindow + { + public: + UINativeWindow() = default; + virtual ~UINativeWindow() = default; + UINativeWindow(UINativeWindow&&) = delete; + UINativeWindow(const UINativeWindow&) = delete; + UINativeWindow& operator=(const UINativeWindow&) = delete; + UINativeWindow& operator=(UINativeWindow&&) = delete; + + //设置窗口标题 + virtual void SetWindowTitle(std::wstring_view title) = 0; + + //获取窗口标题 + virtual std::wstring GetWindowTitle() = 0; + + /*显示或隐藏窗口 + * @param show - 是否显示窗口 + * @param focus - 如果显示窗口 是否将焦点移动到当前窗口 + */ + virtual void ShowWindow(bool show, bool focus = true) = 0; + + //窗口是否可见 + virtual bool IsShowWindow() = 0; + + //设置窗口可用性 + virtual void EnableWindow(bool enable) = 0; + + //窗口是否可用 + virtual bool IsEnableWindow() = 0; + + //窗口是否最小化 + virtual bool IsMinimize() = 0; + + //窗口是否最大化 + virtual bool IsMaximize() = 0; + + //居中显示窗口 + virtual void CenterWindow() = 0; + + //关闭窗口 + virtual void CloseWindow() = 0; + + //设置窗口矩形 + virtual void SetWindowRect(UIRect rect) = 0; + + /*获取窗口矩形 + * @param client - 是否获取客户区矩形 否则获取窗口矩形 + */ + virtual UIRect GetWindowRect(bool client = false) = 0; + + /*设置窗口不透明度 仅分层窗口有效 + * @param alpha - 不透明度0-255 + * @param draw - 是否立即重绘 + */ + virtual void SetWindowAlpha(_m_byte alpha, bool draw = true) = 0; + + //获取窗口不透明度 + virtual _m_byte GetWindowAlpha() = 0; + + //取窗口参数(句柄) + virtual _m_ptrv GetWindowHandle() = 0; + + template + T GetWindowHandle() { return reinterpret_cast(GetWindowHandle()); } + + //设置窗口最小尺寸限制 + virtual void SetMinimSize(UISize min) = 0; + + protected: + //获取窗口原始尺寸 + virtual UISize GetWindowSrcSize() = 0; + }; + } + + namespace Window + { + class UIWindowBasic : public IWindow::UINativeWindow, MThreadT + { + public: + UIWindowBasic(Render::Def::MRender* render); + ~UIWindowBasic() override; + UIWindowBasic(UIWindowBasic&&) = delete; + UIWindowBasic(const UIWindowBasic&) = delete; + UIWindowBasic& operator=(const UIWindowBasic&) = delete; + UIWindowBasic& operator=(UIWindowBasic&&) = delete; + + //获取根控件 + virtual Ctrl::UIControl* GetRootControl() const; + + /*更新布局 并更新显示 + * @param rect - 重绘区域 nullptr = 全部区域 + */ + virtual void UpdateLayout(MPCRect rect); + + /*更新显示 + * @param rect - 更新区域 nullptr = 全部区域 + */ + virtual void UpdateDisplay(MPCRect rect); + + /*设置渲染模式 + * (默认被动渲染) + * @param active - 是否为主动渲染模式 + */ + virtual void SetRenderMode(bool active); + + //获取渲染模式 + virtual bool GetRenderMode() const; + + /*缩放窗口 + * 控件尺寸和位置将按照比例自动计算 + * @param newWidth - 新宽度 + * @param newHeight - 新高度 + */ + virtual void ScaleWindow(_m_uint newWidth, _m_uint newHeight); + + /*缩放窗口 指定缩放比 + * 控件尺寸和位置将按照比例自动计算 + * @param scale - 新缩放比 + */ + virtual void ScaleWindow(_m_scale scale); + + //取窗口缩放比例 + virtual _m_scale GetWindowScale() const; + + /*设定计时器 + * @param elapse - 计时器间隔 + * @param callback - 计时器回调函数 + * + * @return 返回定时器ID + */ + virtual MTimers::ID SetTimer(_m_uint elapse, const MTimerCallback& callback = nullptr); + + /*删除计时器 + * @param id - 计时器ID + */ + virtual void KillTimer(MTimers::ID id); + + /*限制最大渲染帧率 + * @param - fps 设置最大帧速率限制 -1=无限制 + */ + virtual void SetMaxFPSLimit(int fps); + + //获取最后一次绘制的帧率 + virtual _m_uint GetLastFPS() const; + + //设置主窗口标志 + virtual void SetMainWindow(bool main); + + //是否为主窗口 + virtual bool IsMainWindow() const; + + /*设置资源缓存模式 Lite版本无效 + * 启用资源缓存将会利用更多的内存或显存缓存绘制结果来提高渲染效率 + * 并不是所有控件都一定有效果 这取决于控件的支持情况和渲染器的支持情况 + * MiaoUI的内置控件和渲染器均支持该选项 + * param cache - 是否启用资源缓存 + */ + virtual void SetCacheMode(bool cache); + + //获取资源缓存模式 + virtual bool GetResMode(); + + //获取渲染器 + Render::MRenderCmd* GetRender() const; + + //获取资源管理器 + UIResourceMgr* GetResourceMgr() const; + + //获取窗口当前焦点控件 + virtual UIFocus GetFocusControl() const; + + //查找顶级控件 例如菜单 + Ctrl::UIControl* FindTopLevelControl(std::wstring_view name) const; + + /*显示界面库控件的矩形边框 + * 仅供调试使用 会降低性能 + * 设置为true启用 false关闭 默认关闭 + */ + void ShowDebugRect(bool show); + + /*以高亮标记当前焦点控件 + * 仅供调试使用 需要启用调试矩形 + */ + void ShowHighlight(bool show); + + //更新所有控件缓存 + virtual void UpdateCache(); + + //取XMLUI类 + XML::MuiXML* XMLUI() { return m_xmlUI; } + + //批渲染数据 + struct renderData + { + + }; + + protected: + + //设置窗口当前焦点控件 + virtual void SetFocusControl(Ctrl::UIControl* control); + + //设置控件消息捕获 + virtual void SetCapture(Ctrl::UIControl* control); + + //释放控件消息捕获 + virtual void ReleaseCapture(); + + //窗口原始消息处理 + virtual _m_result EventSource(MEventCodeEnum code, _m_param param); + + /*DispatchMouseMessage + * or + * DispatchWindowMessage + */ + bool DispatchControlMessage(Ctrl::UIControl* control, MEventCodeEnum code, _m_param param, bool mouse); + + //鼠标消息处理 + bool EventMouseProc(MEventCodeEnum code, _m_param param); + + /*消息处理过程 + * @param event - 控件消息类型 + * @param control - 发送消息的控件 + * @param param - 控件附加参数 + * + * @return 返回值 + */ + virtual bool EventProc(UINotifyEvent event, Ctrl::UIControl* contorl, _m_param param) = 0; + + //呈现绘制结果 + virtual void Present(Render::MRenderCmd* render, const _m_rect_t* dirtyArea) = 0; + + //初始化渲染器 + virtual bool InitRender(Render::MRenderCmd* render) = 0; + + void SetInited(bool inited); + + void UpdateScale(); + + //创建Node设备资源 调用窗口控件树UINodeBase的OnLoadResource + void LoadNodeResource(bool recreate); + + private: + Render::Def::MRender* m_render = nullptr; //窗口渲染器 + Render::MRenderCmd* m_renderCmd = nullptr; //渲染命令管理器 + UIResourceMgr* m_resourceMgr = nullptr; //资源管理器 + Ctrl::UIControl* m_root = nullptr; //根控件指针 + Ctrl::UIControl* m_toplay = nullptr; //顶级控件指针 + Ctrl::UIControl* m_rootBox = nullptr; //根容器 + _m_scale m_scale = { 1.f,1.f }; //窗口缩放比例 + _m_byte m_alpha = 255; //窗口不透明度 + MTimers* m_timer = nullptr; //窗口定时器 + UIFocus m_focus = { nullptr }; //当前焦点控件 + Ctrl::UIControl* m_capture = nullptr; //当前捕获控件 + Ctrl::UIControl* m_mouseCur = nullptr; //当前鼠标消息控件 + bool m_mouseIn = false; //当前鼠标Hover状态 + MFPSCounter m_fpsCounter; //FPS计数器 + _m_uint m_fpsCache = 0; //当前FPS缓存值 + bool m_isMainWnd = false; //是否为主窗口 + bool m_cacheRes = false; //资源缓存模式 + bool m_inited = false; //是否已初始化完毕 + Render::MNodeRoot* m_renderRoot; + Render::MPenPtr m_dbgFrame = nullptr; + bool m_highlight = false; + bool m_updateCache = true; + + XML::MuiXML* m_xmlUI = nullptr; + + //MQueue<_m_rect_t> m_drawCmdList; //绘制命令队列 + Render::MRingQueue<_m_rect_t> m_drawCmdList; + std::atomic_bool m_renderMode; //是否为主动渲染模式 + + void RenderControlTree(const _m_rect_t* dirtyArea);//渲染控件树 + void ThreadProc(); //独立窗口线程 + void FreeCurMouseCtrl(); //释放当前鼠标控件 + + //线程任务 + struct taskParam + { + std::function task = nullptr; + std::promise& notification; + }; + std::vector m_threadTaskList; + void ExecuteThreadTask(const std::function& task); + + void ResumeThread(); + + friend class Ctrl::UIControl; + friend class Render::UINodeBase; + friend class XML::MuiXML; + friend class Mui::MiaoUI; + friend class Mui::MWindowCtx; + }; + } +} \ No newline at end of file diff --git a/MiaoUI/src/include/Window/Mui_Windows.h b/MiaoUI/src/include/Window/Mui_Windows.h new file mode 100644 index 0000000..8d99d25 --- /dev/null +++ b/MiaoUI/src/include/Window/Mui_Windows.h @@ -0,0 +1,124 @@ +/** + * FileName: Mui_Windows.h + * Note: UI Windows平台窗口声明 + * + * Copyright (C) 2021-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2021-4-22 Create +*/ +#pragma once +#ifdef _WIN32 +#include + +namespace Mui::Window +{ + class UIWindowsWnd : public UIWindowBasic + { + public: + UIWindowsWnd(Render::Def::MRender* render); + UIWindowsWnd(UIWindowsWnd&&) = delete; + UIWindowsWnd(const UIWindowsWnd&) = delete; + UIWindowsWnd& operator=(const UIWindowsWnd&) = delete; + UIWindowsWnd& operator=(UIWindowsWnd&&) = delete; + ~UIWindowsWnd() override; + + /*创建窗口 + * @param parent - 父窗口 + * @param title - 窗口标题 + * @param rect - 窗口矩形 + * @param afterCreated - 窗口创建后回调函数 + * @param style - 窗口样式 WS_ 开头的Windows样式 + * @param exStyle - 扩展窗口样式 WS_EX_ 开头的Windows样式 + * + * @return 创建失败或重复创建或afterCreated返回false 则返回false + */ + bool Create(_m_param parent, std::wstring_view title, UIRect rect, std::function&& afterCreated, _m_param style, _m_param exStyle); + + /*创建窗口 + * @param parent - 父窗口 + * @param title - 窗口标题 + * @param rect - 窗口矩形 + * @param afterCreated - 窗口创建后回调函数 + * @param wndType - 窗口类型 0=默认带系统边框的窗口 1=不带标题栏的普通窗口 2=弹出式无边框窗口(WS_POPUP) 3=分层窗口(WS_EX_LAYERED) + * @param exStyle - 自定义扩展样式附加参数 + * + * @return 创建失败或重复创建或afterCreated返回false 则返回false + */ + bool Create(_m_param parent, std::wstring_view title, UIRect rect, std::function&& afterCreated, int wndType, _m_param exStyle = 0); + + void SetWindowTitle(std::wstring_view title) override; + + std::wstring GetWindowTitle() override; + + void ShowWindow(bool show, bool focus = true) override; + + bool IsShowWindow() override; + + void EnableWindow(bool enable) override; + + bool IsEnableWindow() override; + + bool IsMinimize() override; + + bool IsMaximize() override; + + void CenterWindow() override; + + void CloseWindow() override; + + void SetWindowRect(UIRect rect) override; + + UIRect GetWindowRect(bool client = false) override; + + void SetWindowAlpha(_m_byte alpha, bool draw = true) override; + + _m_byte GetWindowAlpha() override; + + _m_ptrv GetWindowHandle() override; + + void SetMinimSize(UISize min) override; + + void SetVerticalSync(bool sync); + + //将Windows消息码转换为MEventCodeEnum 例如 WM_SIZE -> M_WND_SIZE + MEventCodeEnum ConvertEventCode(_m_uint src); + //将MEventCodeEnum转换为Windows消息码 例如 M_WND_SIZE -> WM_SIZE + _m_uint ConvertEventCode(MEventCodeEnum src); + + protected: + UISize GetWindowSrcSize() override; + + void Present(Render::MRenderCmd* render, const _m_rect_t* rcPaint) override; + + bool InitRender(Render::MRenderCmd* render) override; + + private: + std::wstring_view M_DEF_CLSNAME = L"MiaoUI_Windows"; + + static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + BOOL RegisterWindowClass(); + + HWND m_hWnd = nullptr; + _m_byte m_alpha = 255; + std::wstring m_title; + UISize m_srcSize; + UISize m_minSize; + bool m_layerWnd = false; + bool m_notitleWnd = false; + std::atomic_bool m_VSync = false; + _m_param m_cachePos = 0; + }; +} +#endif // _WIN32 \ No newline at end of file diff --git a/MiaoUI/src/source/Control/Mui_Button.cpp b/MiaoUI/src/source/Control/Mui_Button.cpp new file mode 100644 index 0000000..e62ab72 --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_Button.cpp @@ -0,0 +1,144 @@ +/** + * FileName: Mui_Button.cpp + * Note: UI按钮控件实现 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-19 Create +*/ + +#include + +namespace Mui::Ctrl +{ + UIButton::UIButton(UIControl* parent, Attribute attrib, UILabel::Attribute fontStyle) + : UIButton(std::move(attrib), std::move(fontStyle)) + { + M_ASSERT(parent) + parent->AddChildren(this); + } + + UIButton::UIButton(Attribute attrib, UILabel::Attribute fontStyle) + : UILabel(std::move(fontStyle)), m_attrib(std::move(attrib)) + { + UILabel::OffsetDraw = true; + } + + void UIButton::Register() + { + using namespace CtrlMgr; + auto list = + { + MakeUIAttrib(m_attrib, &Attribute::style, L"style"), + MakeUIAttrib(m_attrib, &Attribute::animate, L"animate"), + MakeUIAttrib(m_attrib, &Attribute::aniAlphaType, L"aniAlphaType"), + MakeUIAttrib(m_attrib, &Attribute::inset, L"inset") + }; + decltype(m_attrib)::RegisterAttrib(list); + + static auto method = [](UIControl* parent) + { + return new UIButton(parent, Attribute()); + }; + MCTRL_REGISTER(method); + } + + void UIButton::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (m_attrib.SetAttribute(attribName, attrib, draw)) + { + //属性已更改 更新缓存 + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + else + UILabel::SetAttribute(attribName, attrib, draw); + } + + std::wstring UIButton::GetAttribute(std::wstring_view attribName) + { + if (std::wstring ret; m_attrib.GetAttribute(attribName, ret)) + return ret; + return UILabel::GetAttribute(attribName); + } + + const UIButton::Attribute& UIButton::GetAttribute() const + { + return m_attrib.Get(); + } + + void UIButton::OnPaintProc(MPCPaintParam param) + { + auto& attrib = m_attrib.Get(); + auto scale = GetRectScale().scale(); + if (attrib.style) + { + attrib.style->PaintStyle(param->render, param->destRect, + param->blendedAlpha, + IsEnabled() ? m_state : UIControlStatus_Disable, 4, scale); + } + UIRect frame = *param->destRect; + const auto& inset = attrib.inset; + if(!(UILabel::GetAttribute().textAlign & TextAlign_Center)) + frame.left += _scale_to(inset.left, scale.cx); + if (!(UILabel::GetAttribute().textAlign & TextAlign_VCenter)) + frame.top += _scale_to(inset.top, scale.cy); + //frame.right -= _scale_to(inset.right, scale.cx); + //frame.bottom -= _scale_to(inset.bottom, scale.cy); + OffsetDrawRc = frame.ToRect(); + UILabel::OnPaintProc(param); + } + + bool UIButton::OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) + { + switch (message) + { + case M_MOUSE_HOVER: + ChangeStatus(UIControlStatus_Hover, UIControlStatus_Normal); + break; + case M_MOUSE_LEAVE: + ChangeStatus(UIControlStatus_Normal, UIControlStatus_Hover); + break; + case M_MOUSE_LBDOWN: + ChangeStatus(UIControlStatus_Pressed, UIControlStatus_Hover); + break; + case M_MOUSE_LBUP: + ChangeStatus(UIControlStatus_Hover, UIControlStatus_Pressed); + break; + } + return UILabel::OnMouseMessage(message, wParam, lParam); + } + + void UIButton::ChangeStatus(UIControlStatus&& state, UIControlStatus&& oldState) + { + m_state = state; + m_cacheUpdate = true; + UpdateDisplay(); + } + + _m_sizef UIButton::GetContentSize() + { + _m_sizef size = UILabel::GetContentSize(); + if (UINodeBase::m_data.AutoSize) + { + auto scale = GetRectScale().scale(); + const auto& inset = m_attrib.Get().inset; + size.width += _scale_to((float)(inset.left + inset.right), scale.cx); + size.height += _scale_to((float)(inset.top + inset.bottom), scale.cy); + } + return size; + } +} \ No newline at end of file diff --git a/MiaoUI/src/source/Control/Mui_CheckBox.cpp b/MiaoUI/src/source/Control/Mui_CheckBox.cpp new file mode 100644 index 0000000..08f771b --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_CheckBox.cpp @@ -0,0 +1,211 @@ +/** + * FileName: Mui_CheckBox.cpp + * Note: UI选择框控件实现 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-11-26 Create +*/ + +#include + +namespace Mui::Ctrl +{ + using namespace Helper; + + UICheckBox::UICheckBox(UIControl* parent, Attribute attrib, UILabel::Attribute fontStyle) + : UICheckBox(std::move(attrib), std::move(fontStyle)) + { + M_ASSERT(parent) + parent->AddChildren(this); + } + + UICheckBox::UICheckBox(Attribute attrib, UILabel::Attribute fontStyle) + : UILabel(std::move(fontStyle)), m_attrib(std::move(attrib)) + { + UILabel::OffsetDraw = true; + } + + void UICheckBox::Register() + { + auto list = + { + MakeUIAttrib(m_attrib, &Attribute::style, L"style"), + MakeUIAttrib(m_attrib, &Attribute::isSel, L"isSel"), + MakeUIAttrib(m_attrib, &Attribute::allowClick, L"allowClick"), + MakeUIAttrib(m_attrib, &Attribute::animate, L"animate"), + MakeUIAttrib(m_attrib, &Attribute::aniAlphaType, L"aniAlphaType"), + MakeUIAttrib(m_attrib, &Attribute::textOffset, L"textOffset") + }; + decltype(m_attrib)::RegisterAttrib(list); + + static auto method = [](UIControl* parent) + { + return new UICheckBox(parent, Attribute()); + }; + MCTRL_REGISTER(method); + } + + void UICheckBox::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (m_attrib.SetAttribute(attribName, attrib, draw)) + { + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + else + UILabel::SetAttribute(attribName, attrib, draw); + } + + std::wstring UICheckBox::GetAttribute(std::wstring_view attribName) + { + if (std::wstring ret; m_attrib.GetAttribute(attribName, ret)) + return ret; + return UILabel::GetAttribute(attribName); + } + + const UICheckBox::Attribute& UICheckBox::GetAttribute() const + { + return m_attrib.Get(); + } + + void UICheckBox::SetSel(bool sel, bool draw) + { + m_attrib.SetAttribute(L"isSel", sel, false); + + switch (m_state) + { + case UICheckBoxHoverSel: + if (!sel) m_state = UICheckBoxHover; + break; + case UICheckBoxHover: + if (sel) m_state = UICheckBoxHoverSel; + break; + case UICheckBoxNormalSel: + if (!sel) m_state = UICheckBoxNormal; + break; + case UICheckBoxNormal: + if (sel) m_state = UICheckBoxNormalSel; + break; + case UICheckBoxDisbaleSel: + if (!sel) m_state = UICheckBoxDisbale; + break; + case UICheckBoxDisbale: + if (sel) m_state = UICheckBoxDisbaleSel; + break; + } + + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + bool UICheckBox::GetSel() const + { + return m_attrib.Get().isSel; + } + + void UICheckBox::OnPaintProc(MPCPaintParam param) + { + UIRect frame = *param->destRect; + + auto& attrib = m_attrib.Get(); + + auto scale = GetRectScale().scale(); + int offset = attrib.textOffset; + offset = _scale_to(offset, scale.cx); + + int height = frame.GetHeight(); + if (UINodeBase::m_data.AutoSize) + frame.right += height + offset; + OffsetDrawRc = { frame.left + height + offset, frame.top, frame.right, frame.bottom }; + + Status tmpStatus = m_state; + if (tmpStatus == UICheckBoxNormal && attrib.isSel) + tmpStatus = UICheckBoxNormalSel; + + if (!IsEnabled()) + { + if (attrib.isSel) + tmpStatus = UICheckBoxDisbaleSel; + else + tmpStatus = UICheckBoxDisbale; + } + + if (attrib.style) + { + UIRect dest = *param->destRect; + dest.right = dest.left + height; + attrib.style->PaintStyle(param->render, &dest, param->blendedAlpha, tmpStatus, 8, scale); + } + UILabel::OnPaintProc(param); + } + + bool UICheckBox::OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) + { + auto& attrib = m_attrib.Get(); + switch ((_m_msg)message) + { + case M_MOUSE_HOVER: + ChangeStatus(attrib.isSel ? UICheckBoxHoverSel : UICheckBoxHover, + attrib.isSel ? UICheckBoxNormalSel : UICheckBoxNormal); + break; + case M_MOUSE_LEAVE: + ChangeStatus(attrib.isSel ? UICheckBoxNormalSel : UICheckBoxNormal, + attrib.isSel ? UICheckBoxHoverSel : UICheckBoxHover); + break; + case M_MOUSE_LBDOWN: + m_isClick = true; + ChangeStatus(attrib.isSel ? UICheckBoxPressedSel : UICheckBoxPressed, + attrib.isSel ? UICheckBoxHoverSel : UICheckBoxHover); + break; + case M_MOUSE_LBUP: + if (m_isClick && attrib.allowClick) + { + m_attrib.SetAttribute(L"isSel", !attrib.isSel, false); + } + ChangeStatus(attrib.isSel ? UICheckBoxHoverSel : UICheckBoxHover, + attrib.isSel ? UICheckBoxPressedSel : UICheckBoxPressed); + break; + } + return UILabel::OnMouseMessage(message, wParam, lParam); + } + + _m_sizef UICheckBox::GetContentSize() + { + _m_sizef size = UINodeBase::GetContentSize(); + if (UINodeBase::m_data.AutoSize) + { + //加上选择区域和文本距离 + int offset = m_attrib.Get().textOffset; + SizeScale(offset, true, true); + + const UISize textSize = CalcSize(); + size = { (float)(textSize.width + textSize.height + offset), (float)textSize.height }; + + size.width = M_MAX(size.width, (float)textSize.width); + size.height = M_MAX(size.height, (float)textSize.height); + } + return size; + } + + void UICheckBox::ChangeStatus(Status&& state, Status&& oldState) + { + m_state = state; + m_cacheUpdate = true; + UpdateDisplay(); + } +} diff --git a/MiaoUI/src/source/Control/Mui_ColorPicker.cpp b/MiaoUI/src/source/Control/Mui_ColorPicker.cpp new file mode 100644 index 0000000..36bfcc2 --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_ColorPicker.cpp @@ -0,0 +1,355 @@ +/** + * FileName: Mui_ColorPicker.cpp + * Note: UI颜色选择器实现 + * + * Copyright (C) 2022-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-9-5 Create +*/ +#include + +#define MAX(a, b, c) ((a)>(b) ? ((a)>(c) ? (a):(c)) : ((b)>(c) ? (b):(c))) +#define MIN(a, b, c) ((a)<(b) ? ((a)<(c) ? (a):(c)) : ((b)<(c) ? (b):(c))) + +namespace Mui::Ctrl +{ + using namespace Color; + + void UIColorPicker::Register() + { + static auto method = [](UIControl* parent) + { + return new UIColorPicker(parent); + }; + CtrlMgr::RegisterControl(ClassName, method); + } + + UIColorPicker::UIColorPicker(UIControl* parent) : UIColorPicker() + { + M_ASSERT(parent) + parent->AddChildren(this); + } + + void UIColorPicker::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (attribName == L"color") + { + SetRGBAColor(Helper::M_GetAttribValueColor(attrib), draw); + } + else if (attribName == L"hsv") + { + std::vector dst; + Helper::M_GetAttribValueInt(attrib, dst, 3); + HSV hsv; + hsv.hue = (_m_ushort)dst[0]; hsv.sat = (_m_ushort)dst[1]; hsv.val = (_m_ushort)dst[2]; + SetHSVColor(hsv, draw); + } + else + UIControl::SetAttribute(attribName, attrib, draw); + } + + std::wstring UIColorPicker::GetAttribute(std::wstring_view attribName) + { + if (attribName == L"color") + { + return M_RGBA_STR(GetRGBAColor()); + } + if (attribName == L"hsv") + { + HSV hsv = GetHSVColor(); + std::wstring ret = std::to_wstring(hsv.hue); + ret += L"," + std::to_wstring(hsv.sat); + ret += L"," + std::to_wstring(hsv.val); + return ret; + } + return UIControl::GetAttribute(attribName); + } + + void UIColorPicker::SetHSVColor(HSV hsv, bool draw) + { + m_hue = hsv.hue; + m_sat = hsv.sat; + m_val = hsv.val; + + UpdateRGB(); + + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + UIColorPicker::HSV UIColorPicker::GetHSVColor() + { + HSV hsv; + hsv.hue = (_m_ushort)round(m_hue); + hsv.sat = (_m_ushort)round(m_sat); + hsv.val = (_m_ushort)round(m_val); + return hsv; + } + + void UIColorPicker::SetRGBAColor(_m_color color, bool draw) + { + m_rgb = M_RGB(M_GetRValue(color), M_GetGValue(color), M_GetBValue(color)); + m_alpha = M_GetAValue(color); + + UpdateHSV(); + + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + _m_color UIColorPicker::GetRGBAColor() + { + return m_rgb | (m_alpha << 24); + } + + void UIColorPicker::OnLoadResource(MRenderCmd* render, bool recreate) + { + UINodeBase::OnLoadResource(render, recreate); + + m_colorBrush = nullptr; + m_bottomBrush = nullptr; + std::pair<_m_color, float> vertex[2]; + vertex[0] = std::make_pair(0, 0.f); + vertex[1] = std::make_pair(M_Black, 1.f); + m_bottomBrush = render->CreateGradientBrush(vertex, 2, { 0, 0 }, { 0, 0 }); + } + + void UIColorPicker::OnPaintProc(MPCPaintParam param) + { + if (!m_bottomBrush) + return; + if ((_m_ushort)m_hue != (_m_ushort)m_hue_old) + { + m_colorBrush = nullptr; + m_hue_old = m_hue; + } + if (!m_colorBrush) + { + float sat = m_sat; + float val = m_val; + _m_color rgb = m_rgb; + + m_sat = 100.f; + m_val = 100.f; + + UpdateRGB(); + _m_color vertcolor = m_rgb | (param->cacheCanvas ? 255 << 24 : UINodeBase::m_data.AlphaDst << 24); + + m_sat = sat; + m_val = val; + m_rgb = rgb; + + std::pair<_m_color, float> vertex[2]; + vertex[0] = std::make_pair(M_White, 0.f); + vertex[1] = std::make_pair(vertcolor, 1.f); + m_colorBrush = param->render->CreateGradientBrush(vertex, 2, { 0, 0 }, { 0, 0 }); + } + + const int width = param->destRect->GetWidth(); + const int height = param->destRect->GetHeight(); + + m_colorBrush->SetStartPoint({ 0, width }); + m_bottomBrush->SetStartPoint({ width, 0 }); + m_colorBrush->SetEndPoint({ height, width }); + m_bottomBrush->SetEndPoint({ width, height }); + + param->render->FillRectangle(*param->destRect, m_colorBrush); + param->render->FillRectangle(*param->destRect, m_bottomBrush); + } + + bool UIColorPicker::OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) + { + switch (message) + { + case M_MOUSE_HOVER: +#ifdef _WIN32 + SetCursor(IDC_CROSS); +#endif // _WIN32 + break; + case M_MOUSE_LEAVE: +#ifdef _WIN32 + SetCursor(IDC_ARROW); +#endif // _WIN32 + break; + case M_MOUSE_LBDOWN: + m_down = true; + SetCapture(); + break; + } + return UIControl::OnMouseMessage(message, wParam, lParam); + } + + bool UIColorPicker::OnMouseMove(_m_uint flag, const UIPoint& point) + { + if (m_down) + { + auto&& frame = Frame(); + float x = float(point.x - frame.left); + float y = float(point.y - frame.top); + float width = (float)frame.GetWidth(); + float height = (float)frame.GetHeight(); + + bool setpos = false; + if (x < 0.f) { + x = 0.f; + setpos = true; + } + else if (x > width) { + x = width; + setpos = true; + } + if (y < 0.f) { + y = 0.f; + setpos = true; + } + else if (y > height) { + y = height; + setpos = true; + } + if (setpos) { +#ifdef _WIN32 + if (auto parent = GetParentWin()) + { + POINT pt = { (int)x + frame.left, (int)y + frame.top }; + ClientToScreen((HWND)parent->GetWindowHandle(), &pt); + SetCursorPos(pt.x, pt.y); + } +#endif // _WIN32 + } + m_sat = x / width * 100.f; + m_val = (height - y) / height * 100.f; + UpdateRGB(); + if (m_rgb_old != m_rgb) + { + m_rgb_old = m_rgb; + SendEvent(Event_ColorPicker_Change, m_rgb | (m_alpha << 24)); + mslot.changed.Emit(m_rgb | (m_alpha << 24)); + } + } + return UIControl::OnMouseMove(flag, point); + } + + bool UIColorPicker::OnLButtonUp(_m_uint flag, const UIPoint& point) + { + if (m_down) + OnMouseMove(flag, point); + m_down = false; + ReleaseCapture(); + return UIControl::OnLButtonUp(flag, point); + } + + bool UIColorPicker::OnSetCursor(_m_param hCur, _m_param lParam) + { +#ifdef _WIN32 + ::SetCursor((HCURSOR)hCur); +#endif // _WIN32 + return true; + } + + void UIColorPicker::UpdateRGB() + { + float sat = m_sat / 100.f; + float val = m_val / 100.f; + + bool zero = false; + if (m_hue == 0.f) + { + zero = true; + m_hue = 359.f; + } + + float base = 255.f * (1.f - sat) * val; + + float r, g, b; + r = g = b = 0; + + switch (_m_ushort(m_hue / 60.f)) + { + case 0: + r = 255.f * val; + g = (255.f * val - base) * (m_hue / 60.f) + base; + b = base; + break; + + case 1: + r = (255.f * val - base) * (1.f - (((_m_ushort)m_hue % 60) / 60.f)) + base; + g = 255.f * val; + b = base; + break; + + case 2: + r = base; + g = 255.f * val; + b = (255.f * val - base) * (((_m_ushort)m_hue % 60) / 60.f) + base; + break; + + case 3: + r = base; + g = (255.f * val - base) * (1.f - (((_m_ushort)m_hue % 60) / 60.f)) + base; + b = 255.f * val; + break; + + case 4: + r = (255.f * val - base) * (((_m_ushort)m_hue % 60) / 60.f) + base; + g = base; + b = 255.f * val; + break; + + case 5: + r = 255.f * val; + g = base; + b = (255.f * val - base) * (1.f - (((_m_ushort)m_hue % 60) / 60.f)) + base; + break; + } + if (zero) + m_hue = 0.f; + m_rgb = M_RGB((_m_byte)r, (_m_byte)g, (_m_byte)b); + } + + void UIColorPicker::UpdateHSV() + { + _m_byte r = M_GetRValue(m_rgb); + _m_byte g = M_GetGValue(m_rgb); + _m_byte b = M_GetBValue(m_rgb); + short temp; + _m_ushort max = MAX(r, g, b); + _m_ushort min = MIN(r, g, b); + _m_ushort delta = max - min; + + if (max == 0) + { + m_hue = m_sat = m_val = 0; + return; + } + + m_val = (float)max / 255.f * 100.f; + m_sat = ((float)delta / max) * 100.f; + + if (r == max) + temp = (short)(60 * ((g - b) / (float)delta)); + else if (g == max) + temp = (short)(60 * (2.0 + (b - r) / (float)delta)); + else + temp = (short)(60 * (4.0 + (r - g) / (float)delta)); + + if (temp < 0) + m_hue = float(temp + 360); + else + m_hue = (float)temp; + } + +} diff --git a/MiaoUI/src/source/Control/Mui_ComBox.cpp b/MiaoUI/src/source/Control/Mui_ComBox.cpp new file mode 100644 index 0000000..16ff050 --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_ComBox.cpp @@ -0,0 +1,383 @@ +/** + * FileName: Mui_Combox.cpp + * Note: UI组合框控件实现 + * + * Copyright (C) 2021-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2021-4-2 Create +*/ + +#include + +namespace Mui::Ctrl +{ + UIComBox::UIComBox(UIControl* parent, Attribute attrib, UIListBox::Attribute listAttrib, + UIScroll::Attribute scrollAttrib, UILabel::Attribute fontStyle) + : UIComBox(attrib, std::move(listAttrib), std::move(scrollAttrib), std::move(fontStyle)) + { + M_ASSERT(parent) + parent->AddChildren(this); + m_root = GetWindowTopCtrl(); + m_root->AddChildren(m_popList); + + m_attrib.SetAttribute(L"listShadowBlur", attrib.listShadowBlur, this); + m_attrib.SetAttribute(L"listShadowColor", attrib.listShadowColor, this); + m_attrib.SetAttribute(L"listShadowOffset", attrib.listShadowOffset, this); + m_attrib.SetAttribute(L"listShadowExtend", attrib.listShadowExtend, this); + } + + UIComBox::UIComBox(Attribute attrib, UIListBox::Attribute listAttrib, UIScroll::Attribute scrollAttrib, + UILabel::Attribute fontStyle) + : UILabel(std::move(fontStyle)), m_attrib(std::move(attrib)) + { + m_popList = new UIListBoxCom(std::move(listAttrib), std::move(scrollAttrib)); + m_popList->SetVisible(false); + m_popList->AutoSize(false, false); + m_popList->combox = this; + } + + UIComBox::~UIComBox() + { + if (m_popList && m_root) + { + m_root->RemoveChildren(m_popList); + delete m_popList; + } + } + + void UIComBox::Register() + { + BindAttribute(); + + static auto method = [](UIControl* parent) + { + return new UIComBox(parent, Attribute()); + }; + MCTRL_REGISTER(method); + } + + void UIComBox::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (attribName == L"listStyle") + m_popList->SetAttribute(L"style", attrib, draw); + else if (m_attrib.SetAttribute(attribName, attrib, this)) + { + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + else if (IsListAttrib(attribName)) + m_popList->SetAttribute(attribName, attrib, draw); + else + UILabel::SetAttribute(attribName, attrib, draw); + } + + std::wstring UIComBox::GetAttribute(std::wstring_view attribName) + { + if (attribName == L"listStyle") + return m_popList->GetAttribute(L"style"); + + std::wstring dst; + if (!m_attrib.GetAttribute(attribName, dst) && IsListAttrib(attribName)) + return m_popList->GetAttribute(attribName); + return UILabel::GetAttribute(attribName); + } + + const UIComBox::Attribute& UIComBox::GetAttribute() const + { + return m_attrib.Get(); + } + + int UIComBox::AddItem(ListItem* item, int index, bool draw) + { + return m_popList->AddItem(item, index, draw); + } + + ListItem* UIComBox::GetItem(int index) const + { + return m_popList->GetItem(index); + } + + bool UIComBox::SetCurSelItem(int index, bool draw) + { + const bool ret = m_popList->SetCurSelItem(index, draw); + if (ret) + { + index = m_popList->GetCurSelItem(); + std::wstring title; + if (index != -1) + title = m_popList->GetItem(index)->GetText(); + UILabel::SetAttribute(L"text", title, draw); + } + return ret; + } + + int UIComBox::GetCurSelItem() const + { + return m_popList->GetCurSelItem(); + } + + int UIComBox::GetItemListCount() const + { + return m_popList->GetItemListCount(); + } + + int UIComBox::DeleteItem(int index, bool draw) + { + return m_popList->DeleteItem(index, false, draw); + } + + int UIComBox::DeleteItem(int index, bool delItem, bool draw) + { + return m_popList->DeleteItem(index, delItem, draw); + } + + void UIComBox::DeleteAllItem(bool delItem, bool draw) + { + m_popList->DeleteAllItem(delItem, draw); + } + + void UIComBox::OnPaintProc(MPCPaintParam param) + { + auto& attrib = m_attrib.Get(); + auto scale = GetRectScale().scale(); + if (attrib.style) + { + attrib.style->PaintStyle(param->render, param->destRect, param->blendedAlpha, + IsEnabled() ? m_state : UIControlStatus_Disable, 4, scale); + } + UILabel::OnPaintProc(param); + //图标绘制 + if (attrib.dropIcon) + { + UIRect dragIcoPos = *param->destRect; + int frameHeight = dragIcoPos.GetHeight(); + if (!attrib.dropIconAutoPos) + { + int xOffset = _scale_to(attrib.dropIconXPos, scale.cx); + dragIcoPos.left = xOffset; + dragIcoPos.right = dragIcoPos.left + frameHeight; + } + else + { + dragIcoPos.left = dragIcoPos.right - frameHeight; + } + param->render->DrawBitmap(attrib.dropIcon->GetBitmap(), param->blendedAlpha, dragIcoPos); + } + } + + bool UIComBox::OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) + { + bool update = true; + switch (message) + { + case M_MOUSE_HOVER: + m_state = UIControlStatus_Hover; + m_isHover = true; + break; + case M_MOUSE_LEAVE: + m_state = UIControlStatus_Normal; + m_isHover = false; + break; + case M_MOUSE_LBDOWN: + m_state = UIControlStatus_Pressed; + m_isClick = true; + break; + case M_MOUSE_LBUP: + if (m_isClick) + { + ShowMenu(!m_popList->IsVisible()); + m_isClick = false; + } + m_state = UIControlStatus_Normal; + break; + default: + update = false; + break; + } + if (update) + { + m_cacheUpdate = true; + UpdateDisplay(); + } + return UILabel::OnMouseMessage(message, wParam, lParam); + } + + bool UIComBox::OnWindowMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam) + { + //如果焦点为弹出的列表框 则不发送失去焦点消息 + if (code == M_WND_KILLFOCUS && reinterpret_cast(wParam) == m_popList) + return true; + return UIControl::OnWindowMessage(code, wParam, lParam); + } + + void UIComBox::OnScale(_m_scale scale) + { + UILabel::OnScale(scale); + m_popList->OnScale(scale); + CalcMenuFrame(); + } + + void UIComBox::OnLayoutCalced() + { + CalcMenuFrame(); + } + + void UIComBox::ShowMenu(bool show) + { + m_popList->SetFocus(true); + if (show) + { + CalcMenuFrame(); + m_popList->UpdateLayout(); + } + + m_popList->SetVisible(show, true); + } + + void UIComBox::CalcMenuFrame() + { + auto& attrib = m_attrib.Get(); + int height = _scale_to(attrib.menuHeight, UINodeBase::m_data.RectScale.hs); + + UIRect rect; + const auto& frame = Frame(); + rect.left = frame.left; + + m_popList->UINodeBase::m_data.EnableDPIScale = UINodeBase::m_data.EnableDPIScale; + m_popList->SetScale({ 1.f, 1.f, UINodeBase::m_data.RectScale.ws, UINodeBase::m_data.RectScale.hs }, false); + + _m_scale scale = { 1.f, 1.f }; + if (UINodeBase::m_data.EnableDPIScale) + { + scale = UINodeBase::m_data.DPIScale; + } + rect.left = int((float)rect.left / (scale.cx / 1.f)); + + if (attrib.popTop) + { + rect.top = frame.top - _scale_to(height, scale.cy); + } + else + rect.top = frame.bottom; + + rect.top = int((float)rect.top / (scale.cy / 1.f)); + m_popList->SetPos(rect.left, rect.top, true); + int width = int((float)frame.GetWidth() / (scale.cx / 1.f)); + m_popList->SetSize(width, height, false); + //m_popList->UpdateLayout(); + m_popList->OnLayoutCalced(); + } + + void UIComBox::SetText(std::wstring_view text) + { + UILabel::SetAttribute(L"text", text); + } + + bool UIComBox::IsListAttrib(std::wstring_view name) + { + if (name == L"itemStyle" + || name == L"itemHeight" + || name == L"lineSpace" + || name == L"iFontStyle" + || name == L"iFont" + || name == L"iFontSize" + || name == L"iFontBold" + || name == L"iFontItalics" + || name == L"iFontUnderline" + || name == L"iFontStrikeout" + || name == L"iFontColor" + || name == L"iTextAlign" + || name == L"iFontCustom" + || name == L"drawOffset" + || name == L"styleV" + || name == L"animate" + || name == L"button" + || name == L"dragValueV" + || name == L"barWidth" + || name == L"barMinHeight" + || name == L"btnHeight" + || name == L"inset") + return true; + return false; + } + + bool UIComBox::UIListBoxCom::OnWindowMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam) + { + if (code == M_WND_KILLFOCUS && !combox->m_isHover) + combox->ShowMenu(false); + return UIControl::OnWindowMessage(code, wParam, lParam); + } + + bool UIComBox::UIListBoxCom::SendEvent(UINotifyEvent event, _m_param param) + { + if (event == Event_ListBox_ItemChanged + || event == Event_ListBox_ItemLClick + || event == Event_ListBox_ItemLDBClick) + { + if (event == Event_ListBox_ItemChanged) + { + combox->SetText(this->GetItem((int)param)->GetText()); + combox->mslot.itemChanged.Emit((int)param); + } + else if (event == Event_ListBox_ItemLClick) + combox->mslot.itemClicked.Emit((int)param); + else if (event == Event_ListBox_ItemLDBClick) + combox->mslot.itemDBClicked.Emit((int)param); + combox->ShowMenu(false); + } + else if(event == Event_ListBox_ItemChanging) + { + combox->mslot.itemChanging.Emit(m_curSelIndex, std::ref(*(bool*)param)); + } + //将列表框消息转到组合框控件发送 + return combox->SendEvent(event, param); + } + + void UIComBox::BindAttribute() + { + auto list = + { + MakeUIAttrib(m_attrib, &Attribute::style, L"style"), + MakeUIAttrib(m_attrib, &Attribute::dropIcon, L"dropIcon"), + MakeUIAttrib(m_attrib, &Attribute::dropIconXPos, L"dropIconXPos"), + MakeUIAttrib(m_attrib, &Attribute::dropIconAutoPos, L"dropIconAutoPos"), + MakeUIAttrib(m_attrib, &Attribute::popTop, L"popTop"), + MakeUIAttrib(m_attrib, &Attribute::menuHeight, L"menuHeight"), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::listShadowBlur, L"listShadowBlur", param, + { + param.param->m_popList->SetAttribute(L"shadowBlur", param.attribValue, false); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::listShadowColor, L"listShadowColor", param, + { + param.param->m_popList->SetAttribute(L"shadowColor", param.attribValue, false); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::listShadowOffset, L"listShadowOffset", param, + { + param.param->m_popList->SetAttribute(L"shadowOffset", param.attribValue, false); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::listShadowExtend, L"listShadowExtend", param, + { + param.param->m_popList->SetAttribute(L"shadowExtend", param.attribValue, false); + return true; + }) + }; + + decltype(m_attrib)::RegisterAttrib(list); + } +} diff --git a/MiaoUI/src/source/Control/Mui_Control.cpp b/MiaoUI/src/source/Control/Mui_Control.cpp new file mode 100644 index 0000000..40dd510 --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_Control.cpp @@ -0,0 +1,716 @@ +/** + * FileName: Mui_Control.cpp + * Note: UI控件基本类型实现 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-17 Create +*/ + +#include +#include + +namespace Mui +{ + using namespace Render; + using namespace Color; + using namespace Window; + + namespace Ctrl + { + + UIControl::UIControl() = default; + + UIControl::~UIControl() = default; + + void UIControl::Register() + { + static auto method = [](UIControl* parent) + { + UIControl* ret = new UIControl(); + parent->AddChildren(ret); + return ret; + }; + CtrlMgr::RegisterControl(ClassName, method); + } + + void UIControl::SetUserData(_m_param data) + { + m_data.UserData = data; + } + + _m_param UIControl::GetUserData() const + { + return m_data.UserData; + } + + void UIControl::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + using namespace Helper; + + if (attribName == L"pos") + { + std::vector dest; + M_GetAttribValueInt(attrib, dest, 2); + SetPos(dest[0], dest[1], draw); + } + else if (attribName == L"size") + { + std::vector dest; + M_GetAttribValueInt(attrib, dest, 2); + SetSize(dest[0], dest[1], draw); + } + else if (attribName == L"minSize") + { + std::vector dest; + M_GetAttribValueInt(attrib, dest, 2); + SetMinSize({ dest[0], dest[1] }, draw); + } + else if (attribName == L"maxSize") + { + std::vector dest; + M_GetAttribValueInt(attrib, dest, 2); + SetMaxSize({ dest[0], dest[1] }, draw); + } + else if (attribName == L"frame") + { + std::vector dest; + M_GetAttribValueInt(attrib, dest, 4); + SetPos(dest[0], dest[1], false); + SetSize(dest[2], dest[3], draw); + } + else if (attribName == L"padding") + { + std::vector dest; + M_GetAttribValueInt(attrib, dest, 4); + SetPadding(UIRect(_m_rect(dest[0], dest[1], dest[2], dest[3])), draw); + } + else if (attribName == L"name") + { + SetName(attrib.data()); + } + else if (attribName == L"data") + { + SetUserData(M_StoLong64(attrib)); + } + else if (attribName == L"visible") + { + SetVisible(attrib == L"false" ? false : true, draw); + } + else if (attribName == L"enable") + { + SetEnabled(attrib == L"false" ? false : true, draw); + } + else if (attribName == L"alpha") + { + SetAlpha((_m_byte)M_StoInt(attrib), draw); + } + else if (attribName == L"enableFocus") + { + SetEnableFocus(attrib == L"false" ? false : true); + } + else if (attribName == L"msgFilter") + { + SetMsgFilter(attrib == L"true" ? true : false); + } + else if (attribName == L"msgLgnore") + { + SetMsgIgnore(attrib == L"true" ? true : false, m_data.MsgIgnoreChild); + } + else if (attribName == L"msgLgnoreChild") + { + SetMsgIgnore(m_data.MsgIgnore, attrib == L"true" ? true : false); + } + else if (attribName == L"bgColor") + { + UIBkgndStyle style = m_bgStyle; + style.bkgndColor = M_GetAttribValueColor(attrib); + SetBackground(style); + } + else if (attribName == L"frameColor") + { + UIBkgndStyle style = m_bgStyle; + style.FrameColor = M_GetAttribValueColor(attrib); + SetBackground(style); + } + else if (attribName == L"frameWidth") + { + UIBkgndStyle style = m_bgStyle; + style.FrameWidth = (_m_ushort)M_StoInt(attrib); + SetBackground(style); + } + else if (attribName == L"frameRound") + { + UIBkgndStyle style = m_bgStyle; + style.RoundValue = M_StoFloat(attrib); + SetBackground(style); + } + else if(attribName == L"shadowColor") + { + UIBkgndStyle style = m_bgStyle; + style.ShadowColor = M_GetAttribValueColor(attrib); + SetBackground(style); + } + else if (attribName == L"shadowOffset") + { + std::vector dest; + M_GetAttribValueInt(attrib, dest, 2); + UIBkgndStyle style = m_bgStyle; + style.ShadowOffset = { dest[0], dest[1] }; + SetBackground(style); + } + else if (attribName == L"shadowExtend") + { + UIBkgndStyle style = m_bgStyle; + style.ShadowExtend = M_StoInt(attrib); + SetBackground(style); + } + else if (attribName == L"shadowRadius") + { + UIBkgndStyle style = m_bgStyle; + style.ShadowRadius = M_StoFloat(attrib); + SetBackground(style); + } + else if (attribName == L"align") + { + SetAlignType((UIAlignment)M_StoInt(attrib), draw); + } + else if(attribName == L"autoSize") + { + AutoSize(attrib == L"false" ? false : true, draw); + } + else if(attribName == L"dpiScale") + { + EnableDPIScale(attrib == L"false" ? false : true); + } + else if(attribName == L"scale") + { + std::vector dst; + M_GetAttribValue(attrib, dst, 4); + _m_rcscale scale; + scale.xs = M_StoFloat(dst[0]); + scale.ys = M_StoFloat(dst[1]); + scale.ws = M_StoFloat(dst[2]); + scale.hs = M_StoFloat(dst[3]); + SetScale(scale, draw); + } + } + + std::wstring UIControl::GetAttribute(std::wstring_view attribName) + { + using namespace CtrlMgr::Attrib; + if (attribName == L"pos") + { + auto pos = GetPos(); + std::wstring value = std::to_wstring(pos.x); + value += L"," + std::to_wstring(pos.y); + return value; + } + if (attribName == L"size") + { + return Value_Make2x(GetSize()); + } + if (attribName == L"minSize") + { + return Value_Make2x(GetMinSize()); + } + if (attribName == L"maxSize") + { + return Value_Make2x(GetMaxSize()); + } + if (attribName == L"frame") + { + auto pos = GetPos(); + auto size = GetSize(); + return Value_Make4x(pos.x, pos.y, size.width, size.height); + } + if (attribName == L"padding") + { + return Value_Make4x(GetPadding()); + } + if (attribName == L"name") + { + return GetName().cstr(); + } + if (attribName == L"data") + { + return std::to_wstring(GetUserData()); + } + if (attribName == L"visible") + { + return IsVisible() ? L"true" : L"false"; + } + if (attribName == L"enable") + { + return IsEnabled() ? L"true" : L"false"; + } + if (attribName == L"alpha") + { + return std::to_wstring(GetAlpha()); + } + if (attribName == L"enableFocus") + { + return m_data.IsFocus ? L"true" : L"false"; + } + if (attribName == L"msgFilter") + { + return m_data.MsgFilter ? L"true" : L"false"; + } + if (attribName == L"msgLgnore") + { + return m_data.MsgIgnore ? L"true" : L"false"; + } + if (attribName == L"msgLgnoreChild") + { + return m_data.MsgIgnoreChild ? L"true" : L"false"; + } + if (attribName == L"bgColor") + { + return M_RGBA_STR(m_bgStyle.bkgndColor); + } + if (attribName == L"frameColor") + { + return M_RGBA_STR(m_bgStyle.FrameColor); + } + if (attribName == L"frameWidth") + { + return std::to_wstring(m_bgStyle.FrameWidth); + } + if (attribName == L"frameRound") + { + return std::to_wstring(m_bgStyle.RoundValue); + } + if (attribName == L"align") + { + return std::to_wstring(GetAlignType()); + } + if (attribName == L"autoSize") + { + return AutoSize() ? L"true" : L"false"; + } + if(attribName == L"dpiScale") + { + return IsDPIScaleEnabled() ? L"true" : L"false"; + } + if(attribName == L"scale") + { + auto scale = GetScale(); + return Value_Make4x(scale.xs, scale.ys, scale.ws, scale.hs); + } + return {}; + } + + void UIControl::SetEnabled(bool enabled, bool draw) + { + m_data.Enabled = enabled; + + std::function setchild = [&setchild, this](UIControl* ctrl, bool enable) + { + ctrl->m_cacheUpdate = true; + ctrl->m_data.ParentEnabled = enable; + + for (auto& child : ctrl->GetNodeList()) + { + if(auto _ctrl = dynamic_cast((UINodeBase*)child)) + setchild(_ctrl, enable); + } + }; + + for (auto& child : GetNodeList()) + { + if (auto ctrl = dynamic_cast((UINodeBase*)child)) + setchild(ctrl, enabled); + } + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + bool UIControl::IsEnabled() const + { + return m_data.Enabled && m_data.ParentEnabled; + } + + UIWindowBasic* UIControl::GetParentWin() const + { + return UINodeBase::m_data.ParentWnd; + } + + void UIControl::AddChildren(UINodeBase* UINode) + { + UINodeBase::AddChildren(UINode); + if (auto ctrl = dynamic_cast(UINode)) + ctrl->m_data.ParentEnabled = IsEnabled(); + } + + void UIControl::SetFocus(bool focus) + { + if (!UINodeBase::m_data.ParentWnd) + return; + if (UINodeBase::m_data.ParentWnd->GetFocusControl().curFocus == this && focus) + return; + if (m_data.IsFocus) + { + UIControl* oldFocus = UINodeBase::m_data.ParentWnd->GetFocusControl().curFocus; + if (oldFocus && oldFocus != this) + oldFocus->OnWindowMessage(M_WND_KILLFOCUS, (_m_param)this, 0); + if (focus) { + UINodeBase::m_data.ParentWnd->SetFocusControl(this); + OnWindowMessage(M_WND_SETFOCUS, (_m_param)this, 0); + } + else { + OnWindowMessage(M_WND_KILLFOCUS, (_m_param)this, 0); + } + } + } + + void UIControl::SetEnableFocus(bool enable) + { + m_data.IsFocus = enable; + } + + void UIControl::SetMsgFilter(bool filter) + { + m_data.MsgFilter = filter; + } + + void UIControl::SetMsgIgnore(bool ignore, bool child) + { + m_data.MsgIgnore = ignore; + m_data.MsgIgnoreChild = child; + } + + void UIControl::ScaleControl(_m_uint width, _m_uint height, bool child) + { + _m_scale newSize; + newSize.cx = (float)width / (float)UINodeBase::m_data.Size.width; + newSize.cy = (float)height / (float)UINodeBase::m_data.Size.height; + ScaleControl({ newSize.cx, newSize.cy }, child); + } + + void UIControl::ScaleControl(_m_scale scale, bool child) + { + class Node : public UINodeBase + { + public: + void SetScaleInternal(_m_rcscale scale) + { + m_data.RectScale = scale; + } + }; + std::function setChild = [this, &setChild](Node* node, _m_rcscale scale) + { + node->SetScaleInternal(scale); + std::vector nodeList; + node->GetChildrenList(nodeList); + + for (auto& child : nodeList) + setChild((Node*)child, scale); + }; + UINodeBase::m_data.RectScale = { 1.f, 1.f, scale.cx, scale.cy }; + if (child) + { + std::vector nodeList; + GetChildrenList(nodeList); + + _m_rcscale _scale; + _scale.xs = scale.cx; + _scale.ys = scale.cy; + _scale.ws = scale.cx; + _scale.hs = scale.cy; + for (auto& _child : nodeList) + setChild((Node*)_child, _scale); + } + UpdateScale(); + UpdateLayout(); + } + + void UIControl::SetCapture() + { + UINodeBase::m_data.ParentWnd->SetCapture(this); + } + + void UIControl::ReleaseCapture() + { + UINodeBase::m_data.ParentWnd->ReleaseCapture(); + } + + bool UIControl::isCapture() + { + return UINodeBase::m_data.ParentWnd->m_capture == this; + } + + MTimers::ID UIControl::SetTimer(_m_uint uTimeout) + { + auto task = [this](_m_param wnd, _m_param id, _m_param time) + { + OnTimer(id); + }; + return UINodeBase::m_data.ParentWnd->SetTimer(uTimeout, task); + } + + void UIControl::KillTimer(MTimers::ID idTimer) + { + return UINodeBase::m_data.ParentWnd->KillTimer(idTimer); + } + + UIControl* UIControl::FindMouseControl(const UIPoint& point) + { + UIControl* ret = this; + + auto& list = GetNodeList(); + for (size_t i = list.size(); i > 0; --i) + { + //类型检查 + UIControl* control = dynamic_cast((UINodeBase*)list[i - 1]); + if (!control || !control->IsVisible() || !control->IsEnabled() + || (control->m_data.MsgIgnore && control->m_data.MsgIgnoreChild) + || !Helper::Rect::IsPtInside(control->Frame(), point)) + continue; + + //忽略消息但不忽略子控件消息 + if(control->m_data.MsgIgnore && !control->m_data.MsgIgnoreChild) + { + ret = control->FindMouseControl(point); + if (ret == control) + { + ret = this; + continue; + } + } + else + ret = control->FindMouseControl(point); + break; + } + return ret; + } + + UIControl* UIControl::GetWindowTopCtrl() const + { + return GetWindowTopCtrl(UINodeBase::m_data.ParentWnd); + } + + UIControl* UIControl::GetWindowTopCtrl(UIWindowBasic* wnd) const + { + if (wnd) + return wnd->m_toplay; + return nullptr; + } + + void UIControl::SetCursor(_m_lpcwstr cursor_name) + { +#ifdef _WIN32 + //::ShowCursor(FALSE); + ::SetCursor(LoadCursorW(NULL, cursor_name)); + //::ShowCursor(TRUE); +#endif // _WIN32 + } + + bool UIControl::OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) + { + UIPoint point = Helper::M_GetMouseEventPt(lParam); + _m_uint flag = (_m_uint)wParam; + bool ret = false; + + switch ((_m_msg)message) + { + case M_MOUSE_HOVER: + ret = OnMouseEntered(flag, point); + break; + case M_MOUSE_LEAVE: + ret = OnMouseExited(flag, point); + break; + case M_MOUSE_WHEEL: + ret = OnMouseWheel(Helper::M_LOWORD((_m_long)wParam), (short)Helper::M_HIWORD((_m_long)wParam), point); + break; + case M_MOUSE_MOVE: + ret = OnMouseMove(flag, point); + break; + case M_MOUSE_LBDOWN: + SetFocus(true); + ret = OnLButtonDown(flag, point); + break; + case M_MOUSE_LBUP: + ret = OnLButtonUp(flag, point); + break; + case M_MOUSE_LBDBCLICK: + SetFocus(true); + ret = OnLButtonDoubleClicked(flag, point); + break; + case M_MOUSE_RBDOWN: + SetFocus(true); + ret = OnRButtonDown(flag, point); + break; + case M_MOUSE_RBUP: + ret = OnRButtonUp(flag, point); + break; + case M_MOUSE_RBDBCLICK: + SetFocus(true); + ret = OnRButtonDoubleClicked(flag, point); + break; + case M_MOUSE_MBDOWN: + SetFocus(true); + ret = OnMButtonDown(flag, point); + break; + case M_MOUSE_MBUP: + ret = OnMButtonUp(flag, point); + break; + case M_MOUSE_MBDBCLICK: + SetFocus(true); + ret = OnMButtonDoubleClicked(flag, point); + break; + case M_SETCURSOR: + ret = OnSetCursor(wParam, lParam); + break; + } + + return ret; + } + + bool UIControl::OnWindowMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam) + { + switch (code) + { + case M_WND_SETFOCUS: + return SendEvent(Event_Focus_True, wParam); + case M_WND_KILLFOCUS: + return SendEvent(Event_Focus_False, wParam); + case M_WND_KEYDOWN: + return SendEvent(Event_Key_Down, wParam); + case M_WND_KEYUP: + return SendEvent(Event_Key_Up, wParam); + default: + break; + } + return false; + } + + bool UIControl::OnMouseEntered(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_Hover, (_m_param)&point); + } + + bool UIControl::OnMouseExited(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_Exited, (_m_param)&point); + } + + bool UIControl::OnLButtonDown(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_LDown, (_m_param)&point); + } + + bool UIControl::OnLButtonUp(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_LUp, (_m_param)&point); + } + + bool UIControl::OnLButtonDoubleClicked(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_LDoubleClicked, (_m_param)&point); + } + + bool UIControl::OnRButtonDown(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_RDown, (_m_param)&point); + } + + bool UIControl::OnRButtonUp(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_RUp, (_m_param)&point); + } + + bool UIControl::OnRButtonDoubleClicked(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_RDoubleClicked, (_m_param)&point); + } + + bool UIControl::OnMButtonDown(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_MDown, (_m_param)&point); + } + + bool UIControl::OnMButtonUp(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_MUp, (_m_param)&point); + } + + bool UIControl::OnMButtonDoubleClicked(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_MDoubleClicked, (_m_param)&point); + } + + bool UIControl::OnMouseWheel(_m_uint flag, short delta, const UIPoint& point) + { + return SendEvent(Event_Mouse_Wheel, (_m_param)delta); + } + + bool UIControl::OnMouseMove(_m_uint flag, const UIPoint& point) + { + return SendEvent(Event_Mouse_Move, (_m_param)&point); + } + + bool UIControl::OnSetCursor(_m_param hCur, _m_param lParam) + { + return SendEvent(Event_Control_SetCursor); + } + + void UIControl::OnTimer(MTimers::ID idTimer) + { + SendEvent(Event_Control_OnTimer, (_m_param)idTimer); + } + + bool UIControl::SendEvent(UINotifyEvent event, _m_param param) + { + if (UINodeBase::m_data.ParentWnd) + return UINodeBase::m_data.ParentWnd->EventProc(event, this, param); + return false; + } + + MPenPtr UIControl::GetDbgFramePen() const + { + if (UINodeBase::m_data.ParentWnd) + return UINodeBase::m_data.ParentWnd->m_dbgFrame; + return nullptr; + } + + bool UIControl::DispatchMouseMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam) + { + bool ret = OnMouseMessage(code, wParam, lParam); + if (m_data.MsgFilter) + { + if (auto control = dynamic_cast(GetParent())) + { + control->DispatchMouseMessage(code, wParam, lParam); + } + } + return ret; + } + + bool UIControl::DispatchWindowMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam) + { + bool ret = OnWindowMessage(code, wParam, lParam); + if (m_data.MsgFilter) + { + for (auto& child : GetNodeList()) + { + if (dynamic_cast((UINodeBase*)child)) + static_cast(child)->DispatchWindowMessage(code, wParam, lParam); + } + } + return ret; + } + } +} \ No newline at end of file diff --git a/MiaoUI/src/source/Control/Mui_EditBox.cpp b/MiaoUI/src/source/Control/Mui_EditBox.cpp new file mode 100644 index 0000000..8e36fcd --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_EditBox.cpp @@ -0,0 +1,1463 @@ +/** + * FileName: Mui_EditBox.cpp + * Note: UI编辑框控件实现 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-28 Create +*/ + +#include + +#include +#include +#pragma comment(lib, "Msimg32.lib") + +#define MTEXTHOST(code) TextHost([&](MTextHost* host) code) +#define MTEXTSERV(code) TxServices([&](ITextServices2* serv) code) + +namespace Mui::Ctrl +{ + using namespace Helper; + + HFONT CreateFontObject(const UILabel::Attribute& attrib, _m_scale scale) + { + const float fontSize = round(M_MIN(scale.cx, scale.cy) * (float)attrib.fontSize); + + LOGFONT lf = { 0 }; + lf.lfHeight = -int(fontSize); + lf.lfCharSet = DEFAULT_CHARSET; + + if (attrib.fontStyle.bold) lf.lfWeight += FW_BOLD; + if (attrib.fontStyle.underline) lf.lfUnderline = TRUE; + if (attrib.fontStyle.italics) lf.lfItalic = TRUE; + if (attrib.fontStyle.strikeout) lf.lfStrikeOut = TRUE; + + wcscpy_s(lf.lfFaceName, attrib.font.cstr()); + + return CreateFontIndirectW(&lf); + } + +#ifdef _WIN32 + UIEditBox::UIEditBox(UIControl* parent, Attribute attrib, UIScroll::Attribute scrollAttrib) + : UIEditBox(std::move(attrib), std::move(scrollAttrib)) + { + M_ASSERT(parent) + parent->AddChildren(this); + } + + UIEditBox::UIEditBox(Attribute attrib, UIScroll::Attribute scrollAttrib) + : UIScroll(std::move(scrollAttrib)), m_attrib(attrib) + { + UIScroll::m_ALLWheel = true; + m_font = CreateFontObject(attrib.fontStyle, GetRectScale().scale()); + OnInitTextSer(); + SetCallback([this](auto&& PH1, auto&& PH2, auto&& PH3) + { + OnScrollView(std::forward(PH1), std::forward(PH2), + std::forward(PH3)); + }); + } + + UIEditBox::~UIEditBox() + { + if (m_font) + DeleteObject(m_font); + if (m_ClearBrush) + DeleteObject(m_ClearBrush); + if (m_oldPaint.PaintBMP) + DeleteObject(m_oldPaint.PaintBMP); + if (m_oldPaint.PaintDC) + DeleteDC(m_oldPaint.PaintDC); + if (m_txtHost) + { + m_txtHost->GetServices()->OnTxInPlaceDeactivate(); + m_txtHost->GetServices()->Release(); + m_txtHost->Release(); + if (m_txtHost->m_hDll) FreeLibrary(m_txtHost->m_hDll); + } + if (CaretTimer) + UIControl::GetParentWin()->KillTimer(CaretTimer); + } + + void UIEditBox::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (m_attrib.SetAttribute(attribName, attrib, this)) + { + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + else + UIScroll::SetAttribute(attribName, attrib, draw); + } + + std::wstring UIEditBox::GetAttribute(std::wstring_view attribName) + { + if (std::wstring ret; m_attrib.GetAttribute(attribName, ret, this)) + return ret; + return UIScroll::GetAttribute(attribName); + } + + void UIEditBox::Register() + { + BindAttribute(); + + static auto method = [](UIControl* parent) + { + return new UIEditBox(parent, Attribute()); + }; + MCTRL_REGISTER(method); + } + + const UIEditBox::Attribute& UIEditBox::GetAttribute() const + { + m_attrib.Set().fontStyle.text = GetCurText(); + return m_attrib.Get(); + } + + void UIEditBox::SetSel(int first, int second, bool bNoScroll) + { + CHARRANGE cr = { first, second }; + LRESULT lResult = 0; + TxSendMessage(EM_EXSETSEL, 0, (LPARAM)&cr, &lResult); + if (bNoScroll) + TxSendMessage(EM_SCROLLCARET, 0, 0L, &lResult); + } + + void UIEditBox::ReplaceSel(std::wstring_view text, bool bCanUndo) + { + TxSendMessage(EM_REPLACESEL, (WPARAM)bCanUndo, (LPARAM)text.data(), nullptr); + } + + bool UIEditBox::GetWordWrap() const + { + return m_attrib.Get().wordWrap; + } + + void UIEditBox::SetWordWrap(bool wrap) + { + m_attrib.Set().wordWrap = wrap; + MTEXTHOST({ host->SetWordWrap(wrap); }); + } + + bool UIEditBox::GetReadOnly() const + { + return m_attrib.Get().readOnly; + } + + void UIEditBox::SetReadOnly(bool read) + { + m_attrib.Set().readOnly = read; + MTEXTHOST({ host->SetReadOnly(read); }); + } + + int UIEditBox::GetLimitText() const + { + return m_attrib.Get().limitText; + } + + void UIEditBox::SetLimitText(int lenght) + { + m_attrib.Set().limitText = lenght; + MTEXTHOST({ host->LimitText(); }); + } + + void UIEditBox::AppendText(std::wstring_view text, bool canUndo) + { + SetSel(-1, -1); + ReplaceSel(text, canUndo); + } + + _m_word UIEditBox::GetEditAlign() const + { + _m_word ret = 0; + MTEXTHOST({ ret = host->GetAlign(); }); + return ret; + } + + void UIEditBox::SetEditAlign(_m_word align) + { + m_attrib.Set().editAlign = align; + MTEXTHOST({ host->SetAlign(align); }); + } + + bool UIEditBox::IsRichMode() const + { + return m_attrib.Get().isRich; + } + + void UIEditBox::SetRichMode(bool rich) + { + m_attrib.Set().isRich = rich; + MTEXTHOST({ host->SetRichTextFlag(rich); }); + } + + void UIEditBox::SetMultiline(bool multiline) + { + m_attrib.Set().multiline = multiline; + MTEXTHOST({ host->SetMultiline(multiline); }); + } + + void UIEditBox::SetNumber(bool number) + { + m_attrib.Set().number = number; + } + + void UIEditBox::SetTextColor(_m_color color) + { + m_attrib.Set().fontStyle.fontColor = color; + MTEXTHOST({ host->SetColor(color); }); + } + + HRESULT UIEditBox::TxSendMessage(UINT msg, WPARAM wparam, LPARAM lparam, LRESULT* plresult) const + { + if (!m_txtHost) + return S_FALSE; + + LRESULT lr = 0; + auto task = [&] + { + lr = m_txtHost->GetServices()->TxSendMessage(msg, wparam, lparam, plresult); + }; + if (m_render) + m_render->RunTask(task); + else + task(); + return (HRESULT)lr; + } + + void UIEditBox::TxServices(std::function task) + { + if (!m_txtHost) return; + if (m_render) + m_render->RunTask([&] { task(m_txtHost->GetServices()); }); + else + task(m_txtHost->GetServices()); + } + + void UIEditBox::OnLoadResource(MRenderCmd* render, bool recreate) + { + UINodeBase::OnLoadResource(render, recreate); + CaretBrush = nullptr; + } + + void UIEditBox::OnPaintProc(MPCPaintParam param) + { + if (!m_txtHost || !m_txtHost->m_services) return; + + auto& attrib = m_attrib.Get(); + + //设置指针画刷 + SetCaretColor(attrib.caretColor, param->render); + + //绘制Style + if (attrib.style && m_txtHost->IsITextHost2()) + { + attrib.style->PaintStyle(param->render, param->destRect, param->blendedAlpha, + IsEnabled() ? m_controlState : UIControlStatus_Disable, 4, GetRectScale().scale()); + } + + UIRect newRect = GetInsetFrame(*param->destRect); + //只支持D2D渲染器 + if (std::wstring_view(param->render->GetRenderName()) == L"GDIPlus") + { + GDIPaint(param->render, param->destRect, &newRect, param->cacheCanvas); + } + if (CaretVisiable && CaretBrush) + { + auto rc = GetCaretRect(); + if (UINodeBase::m_data.SubAtlas) + { + auto frame = Frame(); + rc.left -= frame.left; + rc.right -= frame.left; + rc.top -= frame.top; + rc.bottom -= frame.top; + rc.left += param->destRect->left; + rc.top += param->destRect->top; + rc.right += param->destRect->left; + rc.bottom += param->destRect->top; + } + param->render->FillRectangle(rc, CaretBrush); + } + if (attrib.scroll) + UIScroll::OnPaintProc(param); + } + + void UIEditBox::GDIPaint(MRenderCmd* render, MPCRect destRect, MPCRect insetRect, bool cacheCanvas) + { + UISize curSize = { (int)UINodeBase::m_data.Frame.GetWidth(), (int)UINodeBase::m_data.Frame.GetHeight() }; + if (curSize.width == 0 || curSize.height == 0) + return; + if (curSize != m_oldPaint.bufferSize) + { + if (m_oldPaint.PaintBMP) + DeleteObject(m_oldPaint.PaintBMP); + if (m_oldPaint.PaintDC) + DeleteDC(m_oldPaint.PaintDC); + m_oldPaint.PaintDC = nullptr; + m_txtHost->SetClientRect((UIRect*)insetRect); + } + if (!m_oldPaint.PaintDC) + { + HWND hWnd = (HWND)UINodeBase::m_data.ParentWnd->GetWindowHandle(); + HDC wndDC = GetDC(hWnd); + m_oldPaint.PaintDC = CreateCompatibleDC(wndDC); + ReleaseDC(hWnd, wndDC); + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = curSize.width; + bmi.bmiHeader.biHeight = -curSize.height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = curSize.width * curSize.height * 32 * 8; + + LPVOID pBmpBits = nullptr; + m_oldPaint.PaintBMP = CreateDIBSection(wndDC, &bmi, DIB_RGB_COLORS, &pBmpBits, nullptr, 0); + + if (m_oldPaint.PaintBMP) + { + memset(pBmpBits, 0, curSize.width * curSize.height * 4); + SelectObject(m_oldPaint.PaintDC, m_oldPaint.PaintBMP); + } + m_oldPaint.bufferSize = curSize; + } + auto& attrib = m_attrib.Get(); + if (attrib.style) + { + attrib.style->PaintStyle(render, destRect, cacheCanvas ? 255 : UINodeBase::m_data.AlphaDst, + IsEnabled() ? m_controlState : UIControlStatus_Disable, 4, GetRectScale().scale()); + } + + ALPHAINFO ai; + BLENDFUNCTION bf; + bf.BlendOp = AC_SRC_OVER; + bf.BlendFlags = 0; + bf.AlphaFormat = 0; + bf.SourceConstantAlpha = cacheCanvas ? 255 : UINodeBase::m_data.AlphaDst; + + auto render_ = render->GetBase(); + + HDC pDC = (HDC)render_->GetDC(); + + RECT rc = { 0, 0, curSize.width, curSize.height }; + if (!m_ClearBrush) + m_ClearBrush = CreateSolidBrush(Color::M_RGBA(0, 0, 0, 0)); + FillRect(m_oldPaint.PaintDC, &rc, m_ClearBrush); + + BitBlt(m_oldPaint.PaintDC, 0, 0, curSize.width, curSize.height, pDC, destRect->left, destRect->top, SRCCOPY); + + GdiAlpha::AlphaBackup(m_oldPaint.PaintDC, &rc, ai); + + rc.right = insetRect->GetWidth(); + rc.bottom = insetRect->GetHeight(); + + _m_scale scale = GetRectScale().scale(); + const auto inset = UIScroll::GetAttribute().inset; + rc.left = _scale_to(inset.left, scale.cx); + rc.top = _scale_to(inset.top, scale.cy); + rc.right += rc.left; + rc.bottom += rc.top; + + HRESULT hr = m_txtHost->GetServices()->TxDraw( + DVASPECT_CONTENT, + 0, + nullptr, + nullptr, + m_oldPaint.PaintDC, + nullptr, + (RECTL*)&rc, + nullptr, + nullptr, + nullptr, + NULL, + TXTVIEW_ACTIVE); + + GdiAlpha::AlphaRestore(ai); + + AlphaBlend(pDC, destRect->left, destRect->top, curSize.width, curSize.height, + m_oldPaint.PaintDC, 0, 0, curSize.width, curSize.height, bf); + + render_->ReleaseDC(); + } + + bool UIEditBox::OnWindowMessage(MEventCodeEnum code, _m_param wParam, _m_param lParam) + { + if (!IsEnabled()) + return false; + + LRESULT lResult = 0; + if (m_txtHost) + { + auto message = dynamic_cast(GetParentWin())->ConvertEventCode(code); + if ((message >= WM_KEYFIRST && message <= WM_KEYLAST) || message == WM_CHAR) + { + if ((int)wParam == 67 && GetKeyState(VK_CONTROL) < 0)//Ctrl + C + { + Copy(); + } + if (m_attrib.Get().number && message == WM_CHAR) + { + if (wParam < '0' || wParam > '9') + return TRUE; + } + std::wstring curStr = GetCurTextInternal(); + TxSendMessage(message, (WPARAM)wParam, (LPARAM)lParam, &lResult); + std::wstring changedStr = GetCurTextInternal(); + if (curStr != changedStr) + OnTextChanged(std::move(changedStr)); + + if (message == WM_KEYDOWN) + SendEvent(Event_Key_Down, wParam); + else if (message == WM_KEYUP) + SendEvent(Event_Key_Up, wParam); + + POINT position = { 0 }; + GetCursorPos(&position); + ScreenToClient((HWND)GetParentWin()->GetWindowHandle(), &position); + + MTEXTSERV( + { + serv->OnTxSetCursor(DVASPECT_CONTENT, -1, NULL, NULL, 0, + NULL, 0, position.x, position.y); + }); + CaretVisiable = true; + UpdateDisplay(true); + } + else if (message == WM_SETFOCUS || message == WM_KILLFOCUS) + { + if (message == WM_SETFOCUS) + { + if (!m_attrib.Get().pholderText.empty() + && GetCurTextInternal() == m_attrib.Get().pholderText.view()) + { + SetSel(0, -1); + ReplaceSel(L""); + MTEXTHOST({ host->SetColor(m_attrib.Get().fontStyle.fontColor); }); + } + + ShowCaret(true); + m_controlState = UIControlStatus_Pressed; + m_autoWordSel = false; + if (m_attrib.Get().wordAutoSel) + SelAllText(); + } + else + { + if (GetCurTextLength(GTL_DEFAULT) == 0 && !m_attrib.Get().pholderText.empty()) + { + SetSel(0, -1); + ReplaceSel(m_attrib.Get().pholderText.view()); + MTEXTHOST({ host->SetColor(m_attrib.Get().pholderTextColor); }); + } + + m_controlState = UIControlStatus_Normal; + ShowCaret(false); + } + UpdateDisplay(true); + if (!m_txtHost->IsITextHost2()) + { + MTEXTSERV({ serv->OnTxInPlaceActivate(nullptr); }); + } + TxSendMessage(message, 0, 0, nullptr); + } + + if (message == WM_IME_COMPOSITION || message == WM_SETFOCUS) + { + const HWND hWnd = (HWND)UINodeBase::m_data.ParentWnd->GetWindowHandle(); + if (const HIMC hIMC = ImmGetContext(hWnd)) + { + const _m_rect caret = GetCaretRect(); + COMPOSITIONFORM Composition; + Composition.dwStyle = CFS_POINT; + Composition.ptCurrentPos.x = caret.left; + Composition.ptCurrentPos.y = caret.bottom + 10; + ImmSetCompositionWindow(hIMC, &Composition); + + ImmReleaseContext(hWnd, hIMC); + } + } + } + + return UIScroll::OnWindowMessage(code, wParam, lParam); + } + + bool UIEditBox::OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) + { + if (!UIScroll::OnMouseMessage(message, wParam, lParam)) + { + switch (message) + { + case M_MOUSE_HOVER: + SetCursor(IDC_IBEAM); + if (m_controlState != UIControlStatus_Pressed) + { + m_controlState = UIControlStatus_Hover; + UpdateDisplay(true); + } + break; + case M_MOUSE_LEAVE: + SetCursor(IDC_ARROW); + if (m_controlState != UIControlStatus_Pressed) + { + m_controlState = UIControlStatus_Normal; + UpdateDisplay(true); + } + break; + case M_MOUSE_LBDOWN: + case M_MOUSE_RBDOWN: + CaretVisiable = true; + m_mouseDown = true; + UpdateDisplay(true); + break; + case M_MOUSE_WHEEL: + { + auto cfg = UIScroll::GetAttribute(); + if (cfg.vertical) + message = (MEventCodeEnum)WM_VSCROLL; + else if (cfg.horizontal) + message = (MEventCodeEnum)WM_HSCROLL; + wParam = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? SB_LINEUP : SB_LINEDOWN; + lParam = 0; + } + break; + case M_MOUSE_MOVE: + if (m_mouseDown) + UpdateDisplay(true); + break; + } + if (m_mouseInScroll) + { + m_mouseInScroll = false; + SetCursor(IDC_IBEAM); + } + if (m_txtHost) + { + switch (message) + { + case M_MOUSE_LBDOWN: + case M_MOUSE_RBDOWN: + case M_MOUSE_MBDOWN: + if (!m_autoWordSel && m_attrib.Get().wordAutoSel) + { + m_autoWordSel = true; + return false; + } + break; + } + auto srcCode = dynamic_cast(GetParentWin())->ConvertEventCode(message); + TxSendMessage(srcCode, wParam, lParam, nullptr); + } + } + else if (!m_mouseInScroll && message != M_SETCURSOR) + { + m_mouseInScroll = true; + SetCursor(IDC_ARROW); + } + return message == M_SETCURSOR; + } + + bool UIEditBox::OnSetCursor(_m_param hCur, _m_param lParam) + { + ::SetCursor((HCURSOR)hCur); + SendEvent(Event_Control_SetCursor); + + POINT position = { 0 }; + GetCursorPos(&position); + ScreenToClient((HWND)GetParentWin()->GetWindowHandle(), &position); + + MTEXTSERV({ serv->OnTxSetCursor(DVASPECT_CONTENT, -1, NULL, NULL, 0, + NULL, 0, position.x, position.y); }); + return true; + } + + void UIEditBox::OnTimer(_m_ptrv idTimer) + { + CaretVisiable = !CaretVisiable; + if (UINodeBase::m_data.ParentWnd->GetFocusControl().curFocus != this) + CaretVisiable = false; + m_cacheUpdate = true; + UpdateDisplay(); + } + + void UIEditBox::OnScale(_m_scale scale) + { + UIControl::OnScale(scale); + if (m_font) + DeleteObject(m_font); + m_font = CreateFontObject(m_attrib.Get().fontStyle, GetRectScale().scale()); + MTEXTHOST({ host->SetFont(m_font); }); + } + + void UIEditBox::SetCurText(std::wstring_view text) + { + m_attrib.Set().fontStyle.text = text; + SetSel(0, -1); + if (text.empty() && !m_attrib.Get().pholderText.empty()) + { + ReplaceSel(m_attrib.Get().pholderText.view(), false); + MTEXTHOST({ host->SetColor(m_attrib.Get().pholderTextColor); }); + } + else + ReplaceSel(text.data(), false); + m_cacheUpdate = true; + UpdateDisplay(); + } + + std::wstring UIEditBox::GetCurText() const + { + auto text = GetCurTextInternal(); + if (!m_attrib.Get().pholderText.empty() && text == m_attrib.Get().pholderText.view()) + return {}; + return text; + } + + std::wstring UIEditBox::GetCurSelText() const + { + CHARRANGE cr; + cr.cpMin = cr.cpMax = 0; + TxSendMessage(EM_EXGETSEL, 0, (LPARAM)&cr, nullptr); + + LPWSTR lpText = new wchar_t[cr.cpMax - cr.cpMin + 1]; + ZeroMemory(lpText, (cr.cpMax - cr.cpMin + 1) * sizeof(wchar_t)); + TxSendMessage(EM_GETSELTEXT, 0, (LPARAM)lpText, nullptr); + + std::wstring sText = (LPCWSTR)lpText; + delete[] lpText; + return sText; + } + + int UIEditBox::GetCurTextLength(_m_ulong flag) const + { + GETTEXTLENGTHEX textLenEx; + textLenEx.flags = flag; + textLenEx.codepage = 1200; + LRESULT lResult = 0; + TxSendMessage(EM_GETTEXTLENGTHEX, (WPARAM)&textLenEx, 0, &lResult); + return (int)lResult; + } + + void UIEditBox::SelAllText() + { + return SetSel(0, -1); + } + + void UIEditBox::SetFontStyle(const UILabel::Attribute& fontStyle) + { + if (m_font) + DeleteObject(m_font); + m_font = CreateFontObject(fontStyle, GetRectScale().scale()); + + MTEXTHOST( + { + host->SetFont(m_font); + if (m_attrib.Get().pholderText.empty() || GetCurTextInternal() != m_attrib.Get().pholderText.view()) + host->SetColor(fontStyle.fontColor); + }); + m_attrib.Set().fontStyle = fontStyle; + } + + void UIEditBox::AddColorText(std::wstring_view text, _m_color color) + { + CHARFORMAT2W cf{}; + ZeroMemory(&cf, sizeof(cf)); + + LRESULT lResult; + TxSendMessage(EM_GETCHARFORMAT, 0, (LPARAM)&cf, &lResult); + + cf.cbSize = sizeof(CHARFORMAT2W); + cf.dwMask = CFM_COLOR; + cf.crTextColor = RGB(GetRValue(color), GetGValue(color), GetBValue(color)); + + ReplaceSel(text.data(), FALSE); + int len = GetCurTextLength(0UL); + SetSel(len - (int)text.size(), len); + + TxSendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf, &lResult); + + SetSel(-1, -1); + + TxSendMessage(EM_GETCHARFORMAT, 0, (LPARAM)&cf, &lResult); + TxSendMessage(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf, &lResult); + } + + void UIEditBox::SetPassword(bool password) + { + m_attrib.Set().password = password; + MTEXTSERV({ serv->OnTxPropertyBitsChange(TXTBIT_USEPASSWORD, password ? TXTBIT_USEPASSWORD : 0); }); + } + + void UIEditBox::SetPasswordChar(wchar_t ch) + { + m_attrib.Set().passChar = ch; + MTEXTHOST({ host->SetPasswordChar(ch); }); + } + + bool UIEditBox::Redo() + { + LRESULT lResult; + TxSendMessage(EM_REDO, 0, 0, &lResult); + return lResult == TRUE; + } + + bool UIEditBox::Undo() + { + LRESULT lResult; + TxSendMessage(EM_UNDO, 0, 0, &lResult); + return lResult == TRUE; + } + + void UIEditBox::Clear() + { + SetCurText(L""); + //TxSendMessage(WM_CLEAR, 0, 0, nullptr); + } + + void UIEditBox::Copy() const + { + const std::wstring curStr = GetCurSelText(); + if (OpenClipboard((HWND)UINodeBase::m_data.ParentWnd->GetWindowHandle())) + { + EmptyClipboard(); + HGLOBAL clipBuffer = GlobalAlloc(GMEM_DDESHARE, 2 * curStr.length() + sizeof(wchar_t)); + wchar_t* buffer = (wchar_t*)GlobalLock(clipBuffer); + wcscpy_s(buffer, curStr.length() + 1, curStr.c_str()); + GlobalUnlock(clipBuffer); + SetClipboardData(CF_UNICODETEXT, clipBuffer); + CloseClipboard(); + } + // TxSendMessage(WM_COPY, 0, 0, 0); + } + + void UIEditBox::Cut() + { + TxSendMessage(WM_CUT, 0, 0, nullptr); + } + + void UIEditBox::Paste() + { + TxSendMessage(WM_PASTE, 0, 0, nullptr); + } + + void UIEditBox::OnTextChanged(std::wstring str) + { + SendEvent(Event_Edit_TextChanged, (_m_param)str.data()); + m_attrib.Set().fontStyle.text = str; + mslot.textChanged.Emit(str.data()); + } + + void UIEditBox::CreateCaret(int cx, int cy) + { + CaretSize = { cx, cy }; + } + + void UIEditBox::ShowCaret(bool show) + { + KillTimer(CaretTimer); + if (show) + CaretTimer = SetTimer(500); + CaretVisiable = show; + } + + void UIEditBox::SetCaretPos(int x, int y) + { + CaretPos = { x, y }; + } + + void UIEditBox::SetCaretColor(_m_color color, MRenderCmd* render) + { + CaretColor = color; + if (CaretBrush) + { + CaretBrush->SetColor(color); + } + else + { + CaretBrush = render->CreateBrush(color); + } + } + + _m_rect UIEditBox::GetCaretRect() + { + return { CaretPos.x + 1, CaretPos.y, CaretPos.x + CaretSize.width + 1, CaretPos.y + CaretSize.height }; + } + + void UIEditBox::OnScrollView(UIScroll*, int dragValue, bool horizontal) + { + ShowCaret(false); + + RECT pRect; + MTEXTHOST({ host->TxGetClientRect(&pRect); }); + UISize size = { pRect.right - pRect.left, pRect.bottom - pRect.top }; + int value = CalcOffsetDragValue(horizontal, dragValue, horizontal ? size.width : size.height); + if (!horizontal) + TxSendMessage(WM_VSCROLL, MAKELONG(SB_THUMBTRACK, value), 0, nullptr); + else + TxSendMessage(WM_HSCROLL, MAKELONG(SB_THUMBTRACK, value), 0, nullptr); + } + + void UIEditBox::SetEnabled(bool enable, bool draw) + { + if (!enable) + ShowCaret(false); + UIControl::SetEnabled(enable, draw); + } + + HFONT UIEditBox::GetFont() + { + return m_font; + } + + void UIEditBox::BindAttribute() + { + auto list = + { + MakeUIAttrib(m_attrib, &Attribute::style, L"style"), + MakeUIAttrib(m_attrib, &Attribute::fontStyle, L"fontStyle", + [](decltype(m_attrib)::SetData param) + { + if (auto style = param.GetValue()) + param.param->SetFontStyle(*style); + return true; + }, + [](decltype(m_attrib)::GetData param) + { + return CtrlMgr::ConvertAttribPtr(¶m.data->fontStyle); + }), + MakeUIAttribEx(m_attrib, &UILabel::Attribute::text, &Attribute::fontStyle, L"text", + [](decltype(m_attrib)::SetData param) + { + param.param->SetCurText(param.attribValue); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::multiline, L"multiline", param, + { + param.param->SetMultiline(param.data->multiline); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::password, L"password", param, + { + param.param->SetPassword(param.data->password); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::passChar, L"passChar", param, + { + param.param->SetPasswordChar(param.data->passChar); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::readOnly, L"readOnly", param, + { + param.param->SetReadOnly(param.data->readOnly); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::isRich, L"isRich", param, + { + param.param->SetRichMode(param.data->isRich); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::wordWrap, L"wordWrap", param, + { + param.param->SetWordWrap(param.data->wordWrap); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::wordAutoSel, L"wordAutoSel", param, + { + param.param->MTEXTSERV({ serv->OnTxPropertyBitsChange(TXTBIT_AUTOWORDSEL, param.data->wordAutoSel); }); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::number, L"number", param, + { + param.param->SetNumber(param.data->number); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::scroll, L"scroll", param, + { + param.param->MTEXTSERV({ serv->OnTxPropertyBitsChange(TXTBIT_SCROLLBARCHANGE, TXTBIT_SCROLLBARCHANGE); }); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::autoBar, L"autoBar", param, + { + param.param->MTEXTSERV({ serv->OnTxPropertyBitsChange(TXTBIT_SCROLLBARCHANGE, TXTBIT_SCROLLBARCHANGE); }); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::number, L"number", param, + { + param.param->SetNumber(param.data->number); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::limitText, L"limitText", param, + { + param.param->SetLimitText(param.data->limitText); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::editAlign, L"editAlign", param, + { + param.param->SetEditAlign(param.data->editAlign); + return true; + }), + MakeUIAttrib(m_attrib, &Attribute::caretColor, L"caretColor"), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::pholderText, L"pholderText", param, + { + if (param.param->GetParentWin()->GetFocusControl().curFocus == param.param) + return true; + if (param.data->pholderText.empty()) + return true; + + param.param->SetSel(0, -1); + param.param->ReplaceSel(param.data->pholderText.view()); + param.param->MTEXTHOST({ host->SetColor(param.data->pholderTextColor); }); + + return true; + }), + MakeUIAttrib(m_attrib, &Attribute::pholderTextColor, L"pholderTextColor") + }; + + decltype(m_attrib)::RegisterAttrib(list); + } + + void UIEditBox::TextHost(std::function task) const + { + if (!m_txtHost) return; + if (m_render) + m_render->RunTask([&] { task(m_txtHost); }); + else + task(m_txtHost); + } + + UIRect UIEditBox::GetInsetFrame(UIRect src) + { + _m_scale scale = GetRectScale().scale(); + const auto inset = UIScroll::GetAttribute().inset; + UIRect newRect = src; + newRect.left += _scale_to(inset.left, scale.cx); + newRect.top += _scale_to(inset.top, scale.cy); + newRect.right -= _scale_to(inset.right, scale.cx); + newRect.bottom -= _scale_to(inset.bottom, scale.cy); + return newRect; + } + + void UIEditBox::OnInitTextSer() + { + m_txtHost = new MTextHost(); + if (!m_txtHost) + return; + + if (m_txtHost->Init(this) + && GetCurTextLength(GTL_DEFAULT) == 0 && !m_attrib.Get().pholderText.empty()) + { + SetCurText(m_attrib.Get().pholderText.view()); + SetTextColor(m_attrib.Get().pholderTextColor); + } + } + + std::wstring UIEditBox::GetCurTextInternal() const + { + const int len = GetCurTextLength(GTL_DEFAULT); + GETTEXTEX gt; + gt.flags = GT_DEFAULT; + gt.cb = sizeof(wchar_t) * (len + 1); + gt.codepage = 1200; + LPTSTR lpText = new wchar_t[len + 1]; + ZeroMemory(lpText, (len + 1) * sizeof(wchar_t)); + gt.lpDefaultChar = nullptr; + gt.lpUsedDefChar = nullptr; + TxSendMessage(EM_GETTEXTEX, (WPARAM)>, (LPARAM)lpText, nullptr); + std::wstring sText = lpText; + delete[] lpText; + return sText; + } + +#pragma region MTextHost + + const IID IID_ITextServices = { 0x8d33f740, 0xcf58, 0x11ce, { 0xa8, 0x9d, 0x00, 0xaa, 0x00, 0x6c, 0xad, 0xc5 } }; + + const IID IID_ITextServices2 = { 0x8D33F741, 0xCF58, 0x11CE, { 0xA8, 0x9D, 0x00, 0xAA, 0x00, 0x6C, 0xAD, 0xC5 } }; + + const IID IID_ITextHost = { 0xc5bdd8d0,0xd26e,0x11ce, { 0xa8,0x9e,0x00,0xaa,0x00,0x6c,0xad,0xc5 } }; + + const IID IID_ITextHost2 = { 0x13e670f5,0x1a5a,0x11cf, { 0xab,0xeb,0x00,0xaa,0x00,0xb6,0x5e,0xa1} }; + + ITextServices2* UIEditBox::MTextHost::GetServices() + { + return m_services; + } + + void UIEditBox::MTextHost::SetClientRect(UIRect* prc) + { + //HIMETRIC_PER_INCH = 2540 + LONG extX = (LONG)MulDiv(prc->GetWidth(), 2540, 96); + LONG extY = (LONG)MulDiv(prc->GetHeight(), 2540, 96); + + if (extX == m_sizelExtent.cx && extY == m_sizelExtent.cy) + return; + + m_sizelExtent = { extX, extY }; + + m_services->OnTxPropertyBitsChange(TXTBIT_VIEWINSETCHANGE, TXTBIT_VIEWINSETCHANGE); + } + + void UIEditBox::MTextHost::SetWordWrap(bool warp) + { + m_services->OnTxPropertyBitsChange(TXTBIT_WORDWRAP, warp ? TXTBIT_WORDWRAP : 0); + } + + void UIEditBox::MTextHost::SetReadOnly(bool read) + { + m_services->OnTxPropertyBitsChange(TXTBIT_READONLY, read ? TXTBIT_READONLY : 0); + } + + void UIEditBox::MTextHost::SetFont(HFONT hFont) + { + if (hFont == nullptr) return; + + LOGFONT lf = { 0 }; + GetObjectW(hFont, sizeof(LOGFONT), &lf); + //LY_PER_INCH = 1440 + m_charFormat.yHeight = -lf.lfHeight * 1440 / 96; + + if (lf.lfWeight >= FW_BOLD) + m_charFormat.dwEffects |= CFE_BOLD; + if (lf.lfItalic) + m_charFormat.dwEffects |= CFE_ITALIC; + if (lf.lfUnderline) + m_charFormat.dwEffects |= CFE_UNDERLINE; + m_charFormat.bCharSet = lf.lfCharSet; + m_charFormat.bPitchAndFamily = lf.lfPitchAndFamily; + + wcscpy_s(m_charFormat.szFaceName, lf.lfFaceName); + + m_services->OnTxPropertyBitsChange(TXTBIT_CHARFORMATCHANGE, TXTBIT_CHARFORMATCHANGE); + } + + void UIEditBox::MTextHost::SetColor(COLORREF color) + { + m_charFormat.crTextColor = RGB(GetRValue(color), GetGValue(color), GetBValue(color)); + m_services->OnTxPropertyBitsChange(TXTBIT_CHARFORMATCHANGE, TXTBIT_CHARFORMATCHANGE); + } + + void UIEditBox::MTextHost::LimitText() + { + m_services->OnTxPropertyBitsChange(TXTBIT_MAXLENGTHCHANGE, TXTBIT_MAXLENGTHCHANGE); + } + + WORD UIEditBox::MTextHost::GetAlign() + { + return m_paraFormat.wAlignment; + } + + void UIEditBox::MTextHost::SetAlign(WORD wNewAlign) + { + m_paraFormat.wAlignment = wNewAlign; + m_services->OnTxPropertyBitsChange(TXTBIT_PARAFORMATCHANGE, 0); + } + + void UIEditBox::MTextHost::SetRichTextFlag(bool rich) + { + m_services->OnTxPropertyBitsChange(TXTBIT_RICHTEXT, rich ? TXTBIT_RICHTEXT : 0); + } + + void UIEditBox::MTextHost::SetPasswordChar(WCHAR chars) + { + m_services->OnTxPropertyBitsChange(TXTBIT_USEPASSWORD, (chars != 0) ? TXTBIT_USEPASSWORD : 0); + } + + void UIEditBox::MTextHost::SetMultiline(bool multiline) + { + m_services->OnTxPropertyBitsChange(TXTBIT_MULTILINE, multiline ? TXTBIT_MULTILINE : 0); + } + + void UIEditBox::MTextHost::SetBarWidth(_m_ushort width) + { + m_services->OnTxPropertyBitsChange(TXTBIT_SELBARCHANGE, TXTBIT_SELBARCHANGE); + } + + HRESULT __stdcall UIEditBox::MTextHost::QueryInterface(REFIID riid, void** ppvObject) + { + HRESULT hr = E_NOINTERFACE; + *ppvObject = nullptr; + + if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_ITextHost)) + { + AddRef(); + *ppvObject = dynamic_cast(this); + hr = S_OK; + } + + return hr; + } + + ULONG __stdcall UIEditBox::MTextHost::AddRef() + { + InterlockedIncrement(&m_dwRef); + return m_dwRef; + } + + ULONG __stdcall UIEditBox::MTextHost::Release() + { + ULONG ulRefCount = InterlockedDecrement(&m_dwRef); + if (ulRefCount == 0) + delete this; + return ulRefCount; + } + + BOOL UIEditBox::MTextHost::TxShowScrollBar(INT fnBar, BOOL fShow) + { + auto scroll = dynamic_cast(m_editBox); + if (fnBar == SB_VERT) + { + scroll->SetAttributeSrc(L"vertical", (bool)fShow); + } + else if (fnBar == SB_HORZ) + { + if (!m_editBox->m_attrib.Get().multiline) + return TRUE; + scroll->SetAttributeSrc(L"horizontal", (bool)fShow); + } + else if (fnBar == SB_BOTH) + { + scroll->SetAttributeSrc(L"vertical", (bool)fShow, false); + scroll->SetAttributeSrc(L"horizontal", (bool)fShow); + } + return TRUE; + } + + BOOL UIEditBox::MTextHost::TxSetScrollRange(INT fnBar, LONG nMinPos, INT nMaxPos, BOOL fRedraw) + { + auto scroll = dynamic_cast(m_editBox); + UIRect frame = m_editBox->GetInsetFrame(m_editBox->Frame()); + + if (fnBar == SB_VERT) + { + if (nMaxPos - nMinPos - frame.bottom + frame.top <= 0) + { + scroll->SetAttributeSrc(L"vertical", false); + } + else + { + scroll->SetAttributeSrc(L"vertical", true, false); + scroll->SetAttributeSrc(L"rangeV", nMaxPos - nMinPos); + } + } + else if (fnBar == SB_HORZ) + { + if (nMaxPos - nMinPos - frame.right + frame.left <= 0) + { + scroll->SetAttributeSrc(L"horizontal", false); + } + else + { + scroll->SetAttributeSrc(L"horizontal", true, false); + scroll->SetAttributeSrc(L"rangeH", nMaxPos - nMinPos); + } + } + return TRUE; + } + + BOOL UIEditBox::MTextHost::TxSetScrollPos(INT fnBar, INT nPos, BOOL fRedraw) + { + int range = m_editBox->GetRange(fnBar == SB_HORZ); + RECT pRect; + TxGetClientRect(&pRect); + UISize size = { pRect.right - pRect.left, pRect.bottom - pRect.top }; + int srcRange = range; + //先得到TxHost的可滚动区域 + if (fnBar == SB_HORZ) + srcRange -= size.width; + else + srcRange -= size.height; + if (nPos > srcRange) + nPos = srcRange; + //算出来给的nPos在滚动区域位置的百分比 + float percentage = (float)nPos / (float)srcRange * 100.f; + //然后按百分比算这个位置在我们的滚动条里的位置 + int value = range * (int)percentage / 100; + m_editBox->SetDragValueNoAni(fnBar == SB_HORZ, value, fRedraw); + return TRUE; + } + + void UIEditBox::MTextHost::TxInvalidateRect(LPCRECT prc, BOOL fMode) + { + //m_editBox->m_cacheUpdate = true; + //m_editBox->UpdateDisplay(); + } + + void UIEditBox::MTextHost::TxViewChange(BOOL fUpdate) + { + m_editBox->UpdateDisplay(true); + } + + BOOL UIEditBox::MTextHost::TxCreateCaret(HBITMAP hbmp, INT xWidth, INT yHeight) + { + m_editBox->CreateCaret(xWidth, yHeight); + return TRUE; + } + + BOOL UIEditBox::MTextHost::TxSetCaretPos(INT x, INT y) + { + m_editBox->SetCaretPos(x, y); + return TRUE; + } + + void UIEditBox::MTextHost::TxSetCapture(BOOL fCapture) + { + if (fCapture) + { + m_editBox->SetCapture(); + } + else + { + m_editBox->ReleaseCapture(); + } + } + + void UIEditBox::MTextHost::TxSetFocus() + { + //m_editBox->SetFocus(true); + } + + void UIEditBox::MTextHost::TxSetCursor(HCURSOR hcur, BOOL fText) + { + ::SetCursor(hcur); + } + + BOOL UIEditBox::MTextHost::TxScreenToClient(LPPOINT lppt) + { + return ScreenToClient((HWND)m_editBox->UINodeBase::m_data.ParentWnd->GetWindowHandle(), lppt); + } + + BOOL UIEditBox::MTextHost::TxClientToScreen(LPPOINT lppt) + { + return ClientToScreen((HWND)m_editBox->UINodeBase::m_data.ParentWnd->GetWindowHandle(), lppt); + } + + HRESULT UIEditBox::MTextHost::TxGetClientRect(LPRECT prc) + { + UIRect frame = m_editBox->GetInsetFrame(m_editBox->Frame()); + prc->left = frame.left; + prc->top = frame.top; + prc->right = frame.right; + prc->bottom = frame.bottom; + return S_OK; + } + + HRESULT UIEditBox::MTextHost::TxGetViewInset(LPRECT prc) + { + *prc = { 0,0,0,0 }; + return S_OK; + } + + HRESULT UIEditBox::MTextHost::TxGetCharFormat(const CHARFORMATW** ppCF) + { + *ppCF = &m_charFormat; + return NOERROR; + } + + HRESULT UIEditBox::MTextHost::TxGetParaFormat(const PARAFORMAT** ppPF) + { + *ppPF = &m_paraFormat; + return NOERROR; + } + + COLORREF UIEditBox::MTextHost::TxGetSysColor(int nIndex) + { + return GetSysColor(nIndex); + } + + HRESULT UIEditBox::MTextHost::TxGetBackStyle(TXTBACKSTYLE* pstyle) + { + *pstyle = TXTBACK_TRANSPARENT; + return NOERROR; + } + + HRESULT UIEditBox::MTextHost::TxGetMaxLength(DWORD* plength) + { + *plength = m_editBox->m_attrib.Get().limitText; + return NOERROR; + } + + HRESULT UIEditBox::MTextHost::TxGetScrollBars(DWORD* pdwScrollBar) + { + auto& attrib = m_editBox->m_attrib.Get(); + if (attrib.scroll) + { + *pdwScrollBar |= WS_VSCROLL | WS_HSCROLL; + if (attrib.autoBar) + *pdwScrollBar |= ES_AUTOVSCROLL | ES_AUTOHSCROLL; + } + return NOERROR; + } + + HRESULT UIEditBox::MTextHost::TxGetPasswordChar(TCHAR* pch) + { + *pch = m_editBox->m_attrib.Get().passChar; + return NOERROR; + } + + HRESULT UIEditBox::MTextHost::TxGetExtent(LPSIZEL lpExtent) + { + *lpExtent = m_sizelExtent; + return S_OK; + } + + HRESULT UIEditBox::MTextHost::TxGetPropertyBits(DWORD dwMask, DWORD* pdwBits) + { + DWORD dwProperties = 0; + + const Attribute& config = m_editBox->m_attrib.Get(); + + if (config.isRich) + { + dwProperties = TXTBIT_RICHTEXT; + } + + if (config.multiline) + { + dwProperties |= TXTBIT_MULTILINE; + } + + if (config.readOnly) + { + dwProperties |= TXTBIT_READONLY; + } + + if (config.password) + { + dwProperties |= TXTBIT_USEPASSWORD; + } + + dwProperties |= TXTBIT_HIDESELECTION; + + if (config.wordAutoSel) + { + dwProperties |= TXTBIT_AUTOWORDSEL; + } + + if (config.wordWrap) + { + dwProperties |= TXTBIT_WORDWRAP; + } + //禁用拖拽组件 启用D2D绘制 + dwProperties |= TXTBIT_DISABLEDRAG; + + if (!IsOldVer) + dwProperties |= TXTBIT_D2DDWRITE; + + *pdwBits = dwProperties & dwMask; + return NO_ERROR; + } + + HRESULT UIEditBox::MTextHost::TxGetSelectionBarWidth(LONG* lSelBarWidth) + { + *lSelBarWidth = dynamic_cast(m_editBox)->GetAttribute().barWidth; + return S_OK; + } + + BOOL UIEditBox::MTextHost::Init(UIEditBox* m_pEdit) + { + m_editBox = m_pEdit; + + IUnknown* pUnk = nullptr; + PCreateTextServices TextServicesProc = nullptr; + auto& attrib = m_editBox->m_attrib.Get(); + HRESULT hr = S_OK; + if (FAILED(InitDefaultCharFormat(&m_charFormat, NULL))) goto err; + + memset(&m_paraFormat, 0, sizeof(PARAFORMAT2)); + m_paraFormat.cbSize = sizeof(PARAFORMAT2); + m_paraFormat.dwMask = PFM_ALL; + m_paraFormat.wAlignment = PFA_LEFT; + m_paraFormat.cTabCount = 1; + m_paraFormat.rgxTabs[0] = lDefaultTab; + + //默认在没有窗口的情况下创建的编辑控件是多行的 可以设置段落 + if (!(attrib.editAlign & ES_LEFT)) + { + if (attrib.editAlign & ES_CENTER) + m_paraFormat.wAlignment = PFA_CENTER; + else if (attrib.editAlign & ES_RIGHT) + m_paraFormat.wAlignment = PFA_RIGHT; + } + + IsOldVer = true; + + if (!m_hDll) + m_hDll = LoadLibraryW(L"msftedit.dll"); + TextServicesProc = (PCreateTextServices)GetProcAddress(m_hDll, "CreateTextServices"); + if (m_hDll && TextServicesProc) + { + TextServicesProc(nullptr, dynamic_cast(this), &pUnk); + } + + hr = pUnk->QueryInterface(IID_ITextServices, (void**)&m_services); + pUnk->Release(); + + if (FAILED(hr)) goto err; + + //激活 + m_services->OnTxInPlaceActivate(nullptr); + //设置文本 + if (!attrib.fontStyle.text.empty()) + { + if (FAILED(m_services->TxSetText(attrib.fontStyle.text.cstr()))) + goto err; + } + + return TRUE; + + err: + if (m_hDll) FreeLibrary(m_hDll); + m_hDll = nullptr; + return FALSE; + } + + HRESULT UIEditBox::MTextHost::InitDefaultCharFormat(CHARFORMAT2W* pcf, HFONT hFont) + { + LOGFONT lf = { 0 }; + + if (hFont == nullptr) + { + hFont = m_editBox->GetFont(); + if (hFont == nullptr) return E_FAIL; + } + + GetObjectW(hFont, sizeof(LOGFONT), &lf); + + const DWORD dwColor = m_editBox->m_attrib.Get().fontStyle.fontColor; + pcf->cbSize = sizeof(CHARFORMAT2W); + pcf->crTextColor = RGB(GetRValue(dwColor), GetGValue(dwColor), GetBValue(dwColor)); + pcf->yHeight = -lf.lfHeight * 1440 / 96; + pcf->yOffset = 0; + pcf->dwEffects = 0; + pcf->dwMask = CFM_SIZE | CFM_OFFSET | CFM_FACE | CFM_CHARSET | CFM_COLOR | CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE; + + if (lf.lfWeight >= FW_BOLD) + pcf->dwEffects |= CFE_BOLD; + if (lf.lfItalic) + pcf->dwEffects |= CFE_ITALIC; + if (lf.lfUnderline) + pcf->dwEffects |= CFE_UNDERLINE; + + pcf->bCharSet = lf.lfCharSet; + pcf->bPitchAndFamily = lf.lfPitchAndFamily; + wcscpy_s(pcf->szFaceName, lf.lfFaceName); + + return S_OK; + } +#pragma endregion + +#endif // _WIN32 +} diff --git a/MiaoUI/src/source/Control/Mui_ImgBox.cpp b/MiaoUI/src/source/Control/Mui_ImgBox.cpp new file mode 100644 index 0000000..625d579 --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_ImgBox.cpp @@ -0,0 +1,214 @@ +/** + * FileName: Mui_ImgBox.cpp + * Note: UI图片框控件实现 + * + * Copyright (C) 2020-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-23 Create +*/ + +#include + +#include + +namespace Mui::Ctrl +{ + UIImgBox::UIImgBox(UIControl* parent) : UIImgBox() + { + M_ASSERT(parent) + UINodeBase::m_data.AutoSize = true; + parent->AddChildren(this); + } + + void UIImgBox::Register() + { + static auto method = [](UIControl* parent) + { + return new UIImgBox(parent); + }; + MCTRL_REGISTER(method); + } + + UISize UIImgBox::GetImageSize() const + { + UISize size; + if (m_image) + size = m_image->GetBitmap()->GetSize(); + return size; + } + + void UIImgBox::SetImage(UIBitmapPtr img, bool lowQuality, bool draw) + { + m_isLowQuality = lowQuality; + m_image = std::move(img); + + if (draw) + UpdateDisplay(true); + else + m_cacheUpdate = true; + } + + void UIImgBox::SetImageStyle(ImgStyle style, bool draw) + { + m_style = style; + if (draw) + UpdateDisplay(true); + else + m_cacheUpdate = true; + } + + void UIImgBox::SetQuality(bool lowQuality, bool draw) + { + m_isLowQuality = lowQuality; + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + void UIImgBox::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (attribName == L"img") + { + UIBitmapPtr img = nullptr; + if (auto pImg = (UIBitmap*)Helper::M_StoLong64(attrib)) + img = pImg; + SetImage(img, false, false); + } + else if (attribName == L"imgStyle") + { + ImgStyle imgStyle = (ImgStyle)Helper::M_StoInt(attrib); + SetImageStyle(imgStyle, false); + } + else if(attribName == L"lowQuality") + { + m_isLowQuality = attrib == L"true"; + } + else + { + UIControl::SetAttribute(attribName, attrib, draw); + return; + } + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + std::wstring UIImgBox::GetAttribute(std::wstring_view attribName) + { + if (attribName == L"img") + return std::to_wstring((_m_param)m_image.get()); + if (attribName == L"imgStyle") + return std::to_wstring(m_style); + if (attribName == L"lowQuality") + return m_isLowQuality ? L"true" : L"false"; + return UIControl::GetAttribute(attribName); + } + + //取等比 + inline UISize _GetScale(int size1, int size2, int newSize1) + { + float newSize[2] = { (float)size1,(float)size2 }; + + newSize[1] = (float)newSize1; + newSize[0] = newSize[1] / size1 * size2; + return { (int)newSize[0], (int)newSize[1] }; + } + + void UIImgBox::OnPaintProc(MPCPaintParam param) + { + if (!m_image) + return; + + auto image = m_image->GetBitmap(); + + UIRect m_DrawRect = *param->destRect; + const UISize m_Size = image->GetSize(); + const UIRect m_DrawSrc = { 0, 0, m_Size.width, m_Size.height }; + + switch (m_style) + { + case ImageBoxStyle_Original: + m_DrawRect.right = m_DrawRect.left + m_Size.width; + m_DrawRect.bottom = m_DrawRect.top + m_Size.height; + break; + case ImageBoxStyle_Center: + { + //如果图片大于图片框区域 等比例缩放 + UISize imgSize_new; + + int DrawRcHeight = m_DrawRect.GetHeight(); + int DrawRcWidth = m_DrawRect.GetWidth(); + + //默认按图片框宽度计算 + UISize newSize = _GetScale(m_DrawSrc.right, m_DrawSrc.bottom, DrawRcWidth); + //计算出来的宽度和图片框一样了 但是高度超出了 就按高度来算 + if (newSize.width > DrawRcHeight) + { + newSize = _GetScale(m_DrawSrc.bottom, m_DrawSrc.right, DrawRcHeight); + imgSize_new = newSize; + } + else //如果按宽度算 结果是 高 宽 把它改成 宽 高 + imgSize_new = { newSize.height, newSize.width }; + + //计算居中位置 + const int pos[2] = { (DrawRcWidth - imgSize_new.width) / 2,(DrawRcHeight - imgSize_new.height) / 2 }; + + m_DrawRect = { 0, 0, imgSize_new.width, imgSize_new.height }; + m_DrawRect.Offset(param->destRect->left + pos[0], param->destRect->top + pos[1]); + } + break; + case ImageBoxStyle_ZoomFill: + { + //按高等比例拉伸 + int DrawRcHeight = m_DrawRect.GetHeight(); + int DrawRcWidth = m_DrawRect.GetWidth(); + UIPoint pos; + + int newWidth = Helper::M_CalcAspectRatio(m_Size.width, m_Size.height, DrawRcHeight, false); + int newHeight = DrawRcHeight; + + pos.x = newWidth - DrawRcWidth; + pos.x /= 2;//居中 + if (pos.x != 0) pos.x = -pos.x; + pos.y = 0; + + //按高不足以填充宽 按宽 + if (newWidth < DrawRcWidth) + { + newWidth = DrawRcWidth; + newHeight = Helper::M_CalcAspectRatio(m_Size.width, m_Size.height, DrawRcWidth, true); + pos.x = 0; + pos.y = newHeight - DrawRcHeight; + pos.y /= 2;//居中 + if (pos.y != 0) pos.y = -pos.y; + } + m_DrawRect.left = m_DrawRect.left + pos.x; + m_DrawRect.top = m_DrawRect.top + pos.y; + m_DrawRect.right = m_DrawRect.left + newWidth; + m_DrawRect.bottom = m_DrawRect.top + newHeight; + } + break; + } + param->render->DrawBitmap(image, param->blendedAlpha, m_DrawRect, m_DrawSrc, !m_isLowQuality); + } + + _m_sizef UIImgBox::GetContentSize() + { + auto size = UINodeBase::GetContentSize(); + if (UINodeBase::m_data.AutoSize && m_image) + size = { (float)GetImageSize().width, (float)GetImageSize().height }; + return size; + } +} diff --git a/MiaoUI/src/source/Control/Mui_Label.cpp b/MiaoUI/src/source/Control/Mui_Label.cpp new file mode 100644 index 0000000..7f15c49 --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_Label.cpp @@ -0,0 +1,503 @@ +/** + * FileName: Mui_Label.cpp + * Note: UI标签控件实现 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-23 Create +*/ + +#include +#ifdef __ANDROID__ +#include +#endif + +namespace Mui::Ctrl +{ + using namespace Helper; + + UILabel::UILabel(UIControl* parent, Attribute attrib) : UILabel(std::move(attrib)) + { + M_ASSERT(parent) + parent->AddChildren(this); + } + + UILabel::UILabel(Attribute attrib) : m_attrib(std::move(attrib)) + { + m_cacheSupport = true; + UINodeBase::m_data.AutoSize = true; + } + + void UILabel::Register() + { + BindAttribute(); + + static auto method = [](UIControl* parent) + { + return new UILabel(parent, Attribute()); + }; + MCTRL_REGISTER(method); + } + + void UILabel::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if(!m_attrib.SetAttribute(attribName, attrib, std::make_pair(this, draw))) + UIControl::SetAttribute(attribName, attrib, draw); + } + + std::wstring UILabel::GetAttribute(std::wstring_view attribName) + { + if (std::wstring ret; m_attrib.GetAttribute(attribName, ret)) + return ret; + return UIControl::GetAttribute(attribName); + } + + UISize UILabel::GetTextMetric(bool dpi) const + { + auto attrib = m_attrib.Get(); + if(!dpi && IsDPIScaleEnabled()) + { + m_font->SetFontSize(attrib.fontSize, std::make_pair(0u, (_m_uint)attrib.text.length())); + } + const auto rect = m_font->GetMetrics(); + if(!dpi && IsDPIScaleEnabled()) + { + auto scale = GetRectScale().scale(); + auto fontSize = (_m_ushort)(M_MIN(scale.cx, scale.cy) * (float)attrib.fontSize); + m_font->SetFontSize(fontSize, std::make_pair(0u, (_m_uint)attrib.text.length())); + } + return { rect.GetWidth(), rect.GetHeight() }; + } + + UISize UILabel::CalcSize() + { + const UIRect rect = m_font->GetMetrics(); + UIPoint offset; + if (auto& attrib = m_attrib.Get(); attrib.shadowUse) + { + offset = attrib.shadowOffset; + auto scale = GetRectScale().scale(); + offset.x = _scale_to(offset.x, scale.cx); + offset.y = _scale_to(offset.y, scale.cy); + } + return { rect.GetWidth() + offset.x, rect.GetHeight() + offset.y }; + } + + void UILabel::OnLoadResource(MRenderCmd* render, bool recreate) + { + UINodeBase::OnLoadResource(render, recreate); + + auto& attrib = m_attrib.Get(); + + m_effect = nullptr; + + auto scale = GetRectScale().scale(); + const float fontSize = M_MIN(scale.cx, scale.cy) * (float)attrib.fontSize; + + m_font = render->CreateFonts(attrib.text.cstr(), attrib.font.cstr(), (_m_uint)fontSize, attrib.fontCustom); + m_brush = render->CreateBrush(attrib.fontColor); + m_font->SetFontStyle(attrib.fontStyle, std::make_pair(0u, (_m_uint)attrib.text.length())); + + } + + void UILabel::OnPaintProc(MPCPaintParam param) + { + auto& attrib = m_attrib.Get(); + if (attrib.text.empty()) + return; + + UIRect drawRect = *param->destRect; + if (OffsetDraw) + drawRect = OffsetDrawRc; + + if (!m_brush) return; + + m_brush->SetOpacity(param->blendedAlpha); + + //如果绘制字体阴影 + if (attrib.shadowUse) + { + //如果没有效果 创建一个效果 + if (!m_effect) + m_effect = param->render->CreateEffects(MEffects::GaussianBlur, attrib.shadowBlur); + else + m_effect->SetEffectValue(attrib.shadowBlur); + + m_brush->SetColor(attrib.shadowColor); + + //借用共享画布渲染透明背景文字 + MCanvas* canvasTmp = param->render->GetSharedCanvas(); + //设置渲染对象为临时画布 + param->render->SetCanvas(canvasTmp); + //设置裁剪区 + param->render->PushClipRect(drawRect); + param->render->Clear();//清空临时画布的旧内容 + param->render->DrawTextLayout(m_font, drawRect, m_brush, attrib.textAlign);//绘制字体 + param->render->PopClipRect(); + + //将临时画布上的文字 作为效果输入 渲染到主画布 + if (!param->cacheCanvas) + param->render->SetCanvas(param->render->GetRenderCanvas()); + else + param->render->SetCanvas(UINodeBase::m_data.SubAtlas.get()); + + if (attrib.shadowLow) + { + + drawRect.Offset(attrib.shadowOffset.x, attrib.shadowOffset.y); + param->render->DrawBitmap(canvasTmp, 255, drawRect, drawRect); + drawRect.ResetOffset(); + } + else + { + UIRect _draw_ = drawRect; + _draw_.Offset(attrib.shadowOffset.x, attrib.shadowOffset.y); + param->render->DrawBitmapEffects(canvasTmp, m_effect, 255, _draw_, drawRect); + } + } + + //设置热点颜色 + if (attrib.hyperlink && m_mouseIn) + m_brush->SetColor(attrib.urlColor); + else + m_brush->SetColor(attrib.fontColor); + + param->render->DrawTextLayout(m_font, drawRect, m_brush, attrib.textAlign); + } + + bool UILabel::OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) + { + bool ret = false; + + auto& attrib = m_attrib.Get(); + + switch (message) + { + case M_SETCURSOR: + if (attrib.hyperlink) + ret = true; + break; + case M_MOUSE_HOVER: + m_mouseIn = true; + if (attrib.hyperlink) + { +#ifdef _WIN32 + SetCursor(IDC_HAND); +#endif + m_cacheUpdate = true; + UpdateDisplay(); + ret = true; + } + break; + case M_MOUSE_LEAVE: + m_mouseIn = false; + if (attrib.hyperlink) + { +#ifdef _WIN32 + SetCursor(IDC_ARROW); +#endif + m_cacheUpdate = true; + UpdateDisplay(); + ret = true; + } + break; + case M_MOUSE_LBDOWN: + m_isClick = true; + if (attrib.hyperlink) + { + m_cacheUpdate = true; + UpdateDisplay(); + ret = true; + } + break; + case M_MOUSE_LBUP: + if (m_isClick) + { + if (attrib.hyperlink) + { +#ifdef _WIN32 + SetCursor(IDC_HAND); + if (!attrib.url.empty()) + { + ShellExecuteW(nullptr, L"Open", attrib.url.cstr(), nullptr, nullptr, SW_SHOWNORMAL); + } +#elif __ANDROID__ + if (m_attrib.Url != L"") + JNI::OpenURI(attrib.url); +#else +#error __TODO__ +#endif + } + UIPoint point{ (int)M_LOWORD((_m_long)lParam), (int)M_HIWORD((_m_long)lParam) }; + SendEvent(Event_Mouse_LClick, (_m_param)&point); + + mslot.clicked.Emit(point); + + m_cacheUpdate = true; + UpdateDisplay(); + ret = true; + } + m_isClick = false; + break; + default: + break; + } + UIControl::OnMouseMessage(message, wParam, lParam); + return ret; + } + + void UILabel::OnScale(_m_scale scale) + { + UIControl::OnScale(scale); + auto& attrib = m_attrib.Get(); + + scale = GetRectScale().scale(); + const float fontSize = M_MIN(scale.cx, scale.cy) * (float)attrib.fontSize; + if (m_font) + m_font->SetFontSize((_m_uint)fontSize, std::make_pair(0, (_m_uint)attrib.text.length())); + } + + _m_sizef UILabel::GetContentSize() + { + _m_sizef size = UINodeBase::GetContentSize(); + if (UINodeBase::m_data.AutoSize) + { + const UISize textSize = CalcSize(); + + size.width = M_MAX(size.width, (float)textSize.width); + size.height = M_MAX(size.height, (float)textSize.height); + } + return size; + } + + void UILabel::BindAttribute() + { + using namespace CtrlMgr; + using Type = decltype(m_attrib); + + auto list = + { + MakeUIAttrib(m_attrib, &Attribute::fontStyle, L"fontStyle", + [](Type::SetData param) + { + Attribute defaults; + auto style = param.GetValue(); + if (!style) style = &defaults; + + const auto range = std::make_pair(0u, (_m_uint)style->text.length()); + const auto _this = param.param.first; + + if (style->fontCustom != param.data->fontCustom) + _this->m_font = _this->m_render->CreateFonts(style->text.cstr(), style->font.cstr(), style->fontSize, style->fontCustom); + else + { + _this->m_font->SetText(style->text.cstr()); + _this->m_font->SetFontName(style->font.cstr()); + _this->m_font->SetFontSize(style->fontSize, range); + } + _this->m_font->SetFontStyle(style->fontStyle, range); + + *param.data = *style; + return _this->updateRender(param.param.second, true); + }, + [](Type::GetData param) + { + return CtrlMgr::ConvertAttribPtr(¶m.data->fontStyle); + }), + + decltype(m_attrib)::MakeAttrib(&Attribute::text, L"text", [](Type::SetData param) + { + if (param.data->text.view() == param.attribValue) + return true; + param.Assign(&Attribute::text); + param.param.first->m_font->SetText(param.data->text.cstr()); + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttrib(m_attrib, &Attribute::font, L"font", + [](Type::SetData param) + { + if (param.data->font.view() == param.attribValue) + return true; + + param.data->font = param.attribValue; + param.param.first->m_font->SetFontName(param.data->font.cstr()); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttrib(m_attrib, &Attribute::fontSize, L"fontSize", + [](Type::SetData param) + { + auto fontSize = param.GetValue<_m_ushort>(); + if (param.data->fontSize == fontSize) + return true; + + param.data->fontSize = fontSize; + const auto _this = param.param.first; + + //dpi + auto scale = _this->GetRectScale().scale(); + fontSize = (_m_ushort)(M_MIN(scale.cx, scale.cy) * (float)fontSize); + param.param.first->m_font->SetFontSize(fontSize, { 0u, (_m_uint)param.data->text.length() }); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttribEx(m_attrib, &UIFontStyle::bold, &Attribute::fontStyle, L"fontBold", + [](Type::SetData param) + { + bool value = param.GetValue(); + if (param.data->fontStyle.bold == value) + return true; + + param.data->fontStyle.bold = value; + param.param.first->updateStyle(param.data->fontStyle); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttribEx(m_attrib, &UIFontStyle::italics, &Attribute::fontStyle, L"fontItalics", + [](decltype(m_attrib)::SetData param) + { + bool value = param.GetValue(); + if (param.data->fontStyle.italics == value) + return true; + + param.data->fontStyle.italics = value; + param.param.first->updateStyle(param.data->fontStyle); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttribEx(m_attrib, &UIFontStyle::underline, &Attribute::fontStyle, L"fontUnderline", + [](decltype(m_attrib)::SetData param) + { + bool value = param.GetValue(); + if (param.data->fontStyle.underline == value) + return true; + + param.data->fontStyle.underline = value; + param.param.first->updateStyle(param.data->fontStyle); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttribEx(m_attrib, &UIFontStyle::strikeout, &Attribute::fontStyle, L"fontStrikeout", + [](decltype(m_attrib)::SetData param) + { + bool value = param.GetValue(); + if (param.data->fontStyle.strikeout == value) + return true; + + param.data->fontStyle.strikeout = value; + param.param.first->updateStyle(param.data->fontStyle); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::fontColor, L"fontColor", param, + { + return param.param.first->updateRender(param.param.second); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::hyperlink, L"hyperlink", param, + { + return param.param.first->updateRender(param.param.second); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::url, L"url", param, + { + return param.param.first->updateRender(param.param.second); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::urlColor, L"urlColor", param, + { + return param.param.first->updateRender(param.param.second); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::textAlign, L"textAlign", param, + { + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttrib(m_attrib, &Attribute::fontCustom, L"fontCustom", + [](decltype(m_attrib)::SetData param) + { + auto fontc = param.GetValue<_m_ptrv>(); + if (param.data->fontCustom == fontc) return true; + + param.data->fontCustom = fontc; + const auto _this = param.param.first; + + auto scale = _this->GetRectScale().scale(); + _m_uint fontSize = (_m_uint)(M_MIN(scale.cx, scale.cy) * (float)param.data->fontSize); + _this->m_font = _this->m_render->CreateFonts(param.data->text.cstr(), param.data->font.cstr(), fontSize, fontc); + _this->m_font->SetFontStyle(param.data->fontStyle, std::make_pair(0u, (_m_uint)param.data->text.length())); + + return _this->updateRender(param.param.second, true); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::shadowUse, L"shadowUse", param, + { + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::shadowBlur, L"shadowBlur", param, + { + return param.param.first->updateRender(param.param.second); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::shadowColor, L"shadowColor", param, + { + return param.param.first->updateRender(param.param.second); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::shadowOffset, L"shadowOffset", param, + { + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::shadowLow, L"shadowLow", param, + { + return param.param.first->updateRender(param.param.second); + }), + }; + + decltype(m_attrib)::RegisterAttrib(list); + } + + void UILabel::updateStyle(UIFontStyle& style) + { + m_font->SetFontStyle(style, std::make_pair(0u, (_m_uint)m_attrib.Get().text.length())); + } + + bool UILabel::updateRender(bool draw, bool layout) + { + m_cacheUpdate = true; + if (!draw) return true; + if (layout && UINodeBase::m_data.AutoSize) UpdateLayout(); + else UpdateDisplay(); + return true; + } + + bool UILabel::OnSetCursor(_m_param hCur, _m_param lParam) + { +#ifdef _WIN32 + ::SetCursor((HCURSOR)hCur); +#endif + return true; + } +} diff --git a/MiaoUI/src/source/Control/Mui_ListBox.cpp b/MiaoUI/src/source/Control/Mui_ListBox.cpp new file mode 100644 index 0000000..65f50b9 --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_ListBox.cpp @@ -0,0 +1,795 @@ +/** + * FileName: Mui_ListBox.cpp + * Note: UI列表框控件实现 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-11-28 Create +*/ + +#include + +namespace Mui::Ctrl +{ + using namespace Helper; + +#pragma region ListItem + + void ListItem::OnPaintProc(const PaintParam* param) + { + ItemStatus state = m_state; + if (!m_parent->IsEnabled()) + state = UIListItemDisbale; + if (IsSel()) + state = ItemStatus((int)state + 4); + if (param->style) + PaintStyle(param->render, param->style, *param->dstRect, state, param->dstAlpha); + if (!m_text.empty()) + { + const auto& font = m_parent->m_fontDef; + const auto range = std::make_pair(0u, (_m_uint)m_text.length()); + + font->SetText(m_text.view()); + font->SetFontStyle(m_style, range); + + const float s = M_MIN(param->dstScale.ws, param->dstScale.hs); + font->SetFontSize(_scale_to(m_size, s), range); + + m_parent->m_brushDef->SetColor(m_color == 0 ? param->defcolor : m_color); + m_parent->m_brushDef->SetOpacity(param->dstAlpha); + _m_rect dst = *param->dstRect; + Rect::Offset(&dst, param->offset.x, param->offset.y); + param->render->DrawTextLayout(font, dst, m_parent->m_brushDef, m_parent->m_attrib.Get().fontStyle.textAlign); + } + } + + void ListItem::PaintStyle(MRenderCmd* render, UIStyle* style, _m_rect frame, + ItemStatus state, _m_byte alpha, _m_scale scale) + { + style->PaintStyle(render, &frame, alpha, state, 8, scale); + } + + ListItem::ListItem(std::wstring_view title, _m_color color, UIFontStyle style) + { + m_text = title; + m_color = color; + m_style = style; + } + + ListItem::~ListItem() = default; + + void ListItem::SetText(std::wstring_view text, _m_color color, UIFontStyle style, bool updateStyle) + { + m_text = text; + if (color != 0) + m_color = color; + if (updateStyle) + m_style = style; + } + + void ListItem::SetFontSize(_m_uint size) + { + m_size = size; + } + + std::wstring_view ListItem::GetText() + { + return m_text.view(); + } + + bool ListItem::OnMouseMessage(_m_uint message, _m_param wParam, _m_param lParam) + { + bool paint = true; + switch ((_m_msg)message) + { + case M_MOUSE_HOVER: + { + m_state = UIListItemHover; + break; + } + case M_MOUSE_LEAVE: + { + m_state = UIListItemNormal; + m_isClick = false; + break; + } + case M_MOUSE_RBDOWN: + case M_MOUSE_LBDOWN: + { + m_isClick = true; + m_state = UIListItemPressed; + break; + } + case M_MOUSE_RBUP: + case M_MOUSE_LBUP: + { + if (m_isClick) + { + m_isClick = false; + } + m_state = UIListItemHover; + break; + } + default: + paint = false; + break; + } + return paint; + } + + bool ListItem::IsSel() + { + return m_parent->m_curSelItem == this; + } + +#pragma endregion + +#pragma region UIListBox + + UIListBox::UIListBox(UIControl* parent, Attribute attrib, UIScroll::Attribute scrollAttrib) + : UIListBox(std::move(attrib), std::move(scrollAttrib)) + { + M_ASSERT(parent) + parent->AddChildren(this); + } + + UIListBox::UIListBox(Attribute attrib, UIScroll::Attribute scrollAttrib) + : UIScroll(std::move(scrollAttrib)), m_attrib(std::move(attrib)) + { + m_ALLWheel = true; + SetCallback([this](auto&& PH1, auto&& PH2, auto&& PH3) + { + OnScrollView(std::forward(PH1), std::forward(PH2), + std::forward(PH3)); + }); + } + + UIListBox::~UIListBox() + { + for (auto& item : m_itemList) + { + delete item; + } + } + + void UIListBox::Register() + { + BindAttribute(); + + static auto method = [](UIControl* parent) + { + return new UIListBox(parent, Attribute()); + }; + MCTRL_REGISTER(method); + } + + void UIListBox::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (m_attrib.SetAttribute(attribName, attrib, this)) + { + if (attribName == L"itemHeight" + || attribName == L"lineSpace") + CalcListView(); + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + else + { + UIScroll::SetAttribute(attribName, attrib, draw); + if (attribName == L"inset") + CalcListView(); + } + } + + std::wstring UIListBox::GetAttribute(std::wstring_view attribName) + { + if (std::wstring ret; m_attrib.GetAttribute(attribName, ret)) + return ret; + return UIScroll::GetAttribute(attribName); + } + + const UIListBox::Attribute& UIListBox::GetAttribute() const + { + return m_attrib.Get(); + } + + int UIListBox::AddItem(ListItem* item, int index, bool draw) + { + if (item) + { + if (index == -1) + { + item->m_parent = this; + m_itemList.push_back(item); + CalcListView(); + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + } + else if (IndexCheck(index)) + { + item->m_parent = this; + m_itemList.insert(m_itemList.begin() + index, item); + CalcListView(); + //列表发生改变 需要更新当前选中项目的索引 + if (index < m_curSelIndex) + UpdateIndex(); + if (draw) + UpdateDisplay(); + m_cacheUpdate = true; + } + else + { + MErrorThrow(MErrorCode::IndexOutOfRange); + return (int)m_itemList.size(); + } + } + + return (int)m_itemList.size(); + } + + ListItem* UIListBox::GetItem(int index) const + { + if (index == -1) + { + if (!m_itemList.empty()) + return m_itemList.back(); + } + if (IndexCheck(index)) + return m_itemList[index]; + return nullptr; + } + + bool UIListBox::SetCurSelItem(int index, bool draw) + { + if (m_curSelIndex == index) + return true; + + if (index == -1) + { + m_curSelIndex = -1; + m_curSelItem = nullptr; + } + else if (IndexCheck(index)) + { + m_curSelIndex = index; + m_curSelItem = m_itemList[index]; + } + else + return false; + + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + return true; + } + + int UIListBox::GetCurSelItem() const + { + return m_curSelIndex; + } + + int UIListBox::GetItemListCount() const + { + return (int)m_itemList.size(); + } + + int UIListBox::DeleteItem(int index, bool draw) + { + return DeleteItem(index, false, draw); + } + + int UIListBox::DeleteItem(int index, bool delItem, bool draw) + { + if (index == -1) + { + index = (int)m_itemList.size() - 1; + if (index < 0) index = 0; + } + if (IndexCheck(index)) + { + if (m_curSelIndex == index) + { + m_curSelIndex = -1; + m_curSelItem = nullptr; + } + auto iter = m_itemList.begin() + index; + if (delItem) + delete* iter; + m_itemList.erase(iter); + CalcListView(); + m_cacheUpdate = true; + } + else + MErrorThrow(MErrorCode::IndexOutOfRange); + if (draw) UpdateDisplay(); + return (int)m_itemList.size(); + } + + void UIListBox::DeleteAllItem(bool delItem, bool draw) + { + if (delItem) + { + for (auto& item : m_itemList) + { + delete item; + } + } + m_curSelIndex = -1; + m_curSelItem = nullptr; + m_itemList.clear(); + CalcListView(); + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + } + + void UIListBox::OnLoadResource(MRenderCmd* render, bool recreate) + { + UIScroll::OnLoadResource(render, recreate); + + auto& attrib = m_attrib.Get(); + + auto scale = GetRectScale().scale(); + const float fontSize = M_MIN(scale.cx, scale.cy) * (float)attrib.fontStyle.fontSize; + + m_fontDef = render->CreateFonts(L"", attrib.fontStyle.font.cstr(), (_m_uint)fontSize, attrib.fontStyle.fontCustom); + m_brushDef = render->CreateBrush(attrib.fontStyle.fontColor); + m_fontDef->SetFontStyle(attrib.fontStyle.fontStyle, std::make_pair(0u, 0u)); + } + + void UIListBox::OnPaintProc(MPCPaintParam param) + { + if (auto& attrib = m_attrib.Get(); attrib.style) + { + attrib.style->PaintStyle(param->render, param->destRect, param->blendedAlpha, + IsEnabled() ? m_state : UIControlStatus_Disable, 4, GetRectScale().scale()); + } + PaintListView(param->render, param->cacheCanvas ? param->destRect : param->clipRect, param->cacheCanvas); + UIScroll::OnPaintProc(param); + } + + bool UIListBox::OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) + { + if (!UIScroll::OnMouseMessage(message, wParam, lParam)) + { + bool paint = true; + UIPoint point{ (M_LOWORD((_m_long)lParam)), (M_HIWORD((_m_long)lParam)) }; + + switch (message) + { + case M_MOUSE_HOVER: + m_state = UIControlStatus_Hover; + SendEvent(Event_Mouse_Hover, (_m_param)&point); + break; + case M_MOUSE_LEAVE: + m_state = UIControlStatus_Normal; + SendEvent(Event_Mouse_Exited, (_m_param)&point); + break; + case M_MOUSE_LBDOWN: + case M_MOUSE_RBDOWN: + if (message == M_MOUSE_RBDOWN && !m_attrib.Get().allowRightSel) + break; + + m_state = UIControlStatus_Pressed; + m_downViewY = m_view.top - point.y; + SendEvent(message == M_MOUSE_RBDOWN ? Event_Mouse_RDown : Event_Mouse_LDown, (_m_ptrv)&point); + //移动端可能直接触发LBDOWN消息而不先触发MOVE 所以LBDOWN的时候查找一下hover控件 + paint = DispatchItemMsg(point, message, wParam, lParam); + goto End; + case M_MOUSE_LBUP: + case M_MOUSE_RBUP: + { + if (message == M_MOUSE_RBUP && !m_attrib.Get().allowRightSel) + break; + + m_state = UIControlStatus_Normal; + if (m_hoverIndex && m_hoverIndex->m_isClick) + SetSelItem(m_hoverIndex, point); + SendEvent(message == M_MOUSE_RBUP ? Event_Mouse_RUp : Event_Mouse_LUp, (_m_param)&point); + } + break; + + case M_MOUSE_MOVE: + + paint = DispatchItemMsg(point, message, wParam, lParam); + SendEvent(Event_Mouse_Move, (_m_param)&point); + break; + default: + paint = false; + break; + } + + if (m_hoverIndex) + { + bool ret = m_hoverIndex->OnMouseMessage(message, wParam, lParam); + if (!paint && ret) + paint = true; + if (message == M_MOUSE_LEAVE) + m_hoverIndex = nullptr; + } + + End: + if (paint) + { + m_cacheUpdate = true; + UpdateDisplay(); + } + return paint; + } + return true; + } + + void UIListBox::OnScrollView(UIScroll* ptr, int dragValue, bool horizontal) + { + if (!horizontal) + { + int value = CalcOffsetDragValue(horizontal, dragValue, m_view.viewHeight); + SetViewTop(-value); + } + } + + bool UIListBox::DispatchItemMsg(UIPoint& point, _m_uint msg, _m_param wParam, _m_param lParam) + { + //如果当前有焦点 而在区域内 + if (m_hoverIndex && Rect::IsPtInside(m_curItemRect, point)) + return m_hoverIndex->OnMouseMessage(msg, wParam, lParam); + + //鼠标位置移动到了别的控件 重新查找当前焦点 + for (int i = 0; i < m_view.indexCount; ++i) + { + auto index = m_view.beginIndex + i; + if (index == (int)m_itemList.size()) break; + + auto item = m_itemList[index]; + auto& itemRect = m_view.viewItemRect[i]; + + if (Rect::IsPtInside(itemRect, point)) + { + if (m_hoverIndex != nullptr && m_hoverIndex != item) + { + m_hoverIndex->OnMouseMessage(M_MOUSE_LEAVE, wParam, lParam); + item->OnMouseMessage(M_MOUSE_HOVER, wParam, lParam); + } + else if (m_hoverIndex == nullptr) + { + item->OnMouseMessage(M_MOUSE_HOVER, wParam, lParam); + } + m_hoverIndex = item; + m_curItemRect = itemRect; + return true; + } + } + //如果没找到焦点 移动到了空白区域 给当前焦点发送离开通知 + if (m_hoverIndex) + { + bool ret = m_hoverIndex->OnMouseMessage(M_MOUSE_LEAVE, wParam, lParam); + m_hoverIndex = nullptr; + return ret; + } + return false; + } + + void UIListBox::SetViewTop(int top) + { + m_view.top = top; + //一个项目+一个间距为一组 + int height = m_view.itemHeight + m_view.lineSpace; + int beginIndex = 0; + UIPoint offsetPos; + + //计算当前开始索引 + if (top != 0) + { + top = -top; + int offset = top / height; + + beginIndex = offset; + + offsetPos.y = top % height;//取余数 + offsetPos.y = -offsetPos.y;//还原成负数 + } + m_view.beginIndex = beginIndex; + m_view.offsetPos = offsetPos; + } + + bool UIListBox::IndexCheck(int index) const + { + if (index < (int)m_itemList.size() && index >= 0) + return true; + return false; + } + + void UIListBox::CalcListView() + { + auto& attrib = m_attrib.Get(); + auto& scroll = UIScroll::GetAttribute(); + //DPI + _m_rcscale scale = GetRectScale(); + auto lineSpace = _scale_to(attrib.lineSpace, scale.hs); + auto itemHeight = _scale_to(attrib.itemHeight, scale.hs); + UIRect boxRect = Frame(); + const auto& inset = scroll.inset; + boxRect.left += _scale_to(inset.left, scale.ws); + boxRect.top += _scale_to(inset.top, scale.hs); + boxRect.right -= _scale_to(inset.right, scale.ws); + boxRect.bottom -= _scale_to(inset.bottom, scale.hs); + + m_view.scale = scale; + m_view.viewHeight = boxRect.GetHeight(); + m_view.boxRect = boxRect; + m_view.itemHeight = itemHeight; + m_view.lineSpace = lineSpace; + + //计算视图位置 + itemHeight += lineSpace; + m_view.bottom = (_m_long)(m_itemList.size() * itemHeight) - lineSpace; + //没有项目 + if (m_view.bottom < 0) m_view.bottom = 0; + + //设置滚动条属性 + if (scroll.range.height != m_view.bottom) + { + bool vertical; + + if (m_view.viewHeight < m_view.bottom) + vertical = true; + else + vertical = false; + + UIScroll::SetAttributeSrc(L"rangeV", m_view.bottom, false); + UIScroll::SetAttributeSrc(L"vertical", vertical, true); + } + else + UIScroll::SetAttributeSrc(L"vertical", m_view.viewHeight < m_view.bottom, true); + + if (m_view.viewHeight > m_view.bottom) + { + SetViewTop(0); + SetDragValueNoAni(false, 0, false); + } + //计算可视项目数量 + m_view.indexCount = Helper::M_MAX((int)ceil((float)m_view.viewHeight / (float)itemHeight) + 1, 0); + m_view.viewItemRect.resize(m_view.indexCount); + } + + void UIListBox::PaintListView(MRenderCmd* render, MPCRect destRect, bool cache) + { + UIRect boxRect = m_view.boxRect; + if (cache) + boxRect.Offset(-(int)UINodeBase::m_data.Frame.left, -(int)UINodeBase::m_data.Frame.top); + UIRect itemRect = UIRect(boxRect.left, boxRect.top + m_view.offsetPos.y, boxRect.GetWidth(), m_view.itemHeight); + if(cache) + itemRect.Offset(destRect->left, destRect->top); + + //重设裁剪区 + render->PopClipRect(); + _m_rect clip = boxRect; + Rect::Intersect(&clip, destRect, &clip); + render->PushClipRect(clip); + + auto& attrib = m_attrib.Get(); + UIPoint _offset = { _scale_to(attrib.drawOffset.x, m_view.scale.ws), _scale_to(attrib.drawOffset.y, m_view.scale.hs) }; + + ListItem::PaintParam param; + param.render = render; + param.dstAlpha = cache ? 255 : UINodeBase::m_data.AlphaDst; + param.style = m_attrib.Get().itemStyle.get(); + param.dstScale = m_view.scale; + param.defcolor = m_attrib.Get().fontStyle.fontColor; + for (int i = 0; i < m_view.indexCount; ++i) + { + if (m_view.beginIndex + i >= (int)m_itemList.size()) break; + + auto item = m_itemList[m_view.beginIndex + i]; + m_view.viewItemRect[i] = itemRect; + if (cache) + { + m_view.viewItemRect[i].Offset((int)UINodeBase::m_data.Frame.left, (int)UINodeBase::m_data.Frame.top); + m_view.viewItemRect[i].Offset(-destRect->left, -destRect->top); + } + auto dstRect = itemRect; + param.dstRect = &dstRect; + param.offset = _offset; + item->OnPaintProc(¶m); + + if (auto&& dbgframe = GetDbgFramePen()) + render->DrawRectangle(dstRect, dbgframe); + + itemRect.Offset(0, m_view.itemHeight + m_view.lineSpace); + } + } + + void UIListBox::SetSelItem(ListItem* item, UIPoint pt) + { + if (m_curSelItem != item) + { + int oldIndex = m_curSelIndex; + auto oldItem = m_curSelItem; + + m_curSelItem = item; + UpdateIndex(); + SendEvent(Event_ListBox_ItemLClick, (_m_param)m_curSelIndex); + mslot.itemClicked.Emit(pt, m_curSelIndex); + + bool change = true; + SendEvent(Event_ListBox_ItemChanging, (_m_param)&change); + mslot.itemChanging.Emit(m_curSelIndex, std::ref(change)); + + //如果用户指定不更改 还原 + if (!change) + { + m_curSelIndex = oldIndex; + m_curSelItem = oldItem; + return; + } + SendEvent(Event_ListBox_ItemChanged, (_m_param)m_curSelIndex); + mslot.itemChanged.Emit(m_curSelIndex); + } + else + { + SendEvent(Event_ListBox_ItemLClick, (_m_param)m_curSelIndex); + mslot.itemClicked.Emit(pt, m_curSelIndex); + } + } + + void UIListBox::UpdateIndex() + { + //查找当前选中项目的索引值 + auto iter = std::find(m_itemList.begin(), m_itemList.end(), m_curSelItem); + if (iter == m_itemList.end()) + { + m_curSelIndex = -1; + m_curSelItem = nullptr; + } + else + { + m_curSelIndex = (int)std::distance(m_itemList.begin(), iter); + } + } + + void UIListBox::BindAttribute() + { + using Type = decltype(m_attrib); + + const auto fontStyleOffset = Type::MOffsetOf(&ItemFont::fontStyle) + Type::MOffsetOf(&Attribute::fontStyle); + + auto list = + { + MakeUIAttrib(m_attrib, &Attribute::style, L"style"), + MakeUIAttrib(m_attrib, &Attribute::itemStyle, L"itemStyle"), + MakeUIAttrib(m_attrib, &Attribute::itemHeight, L"itemHeight"), + MakeUIAttrib(m_attrib, &Attribute::allowRightSel, L"allowRightSel"), + MakeUIAttrib(m_attrib, &Attribute::lineSpace, L"lineSpace"), + MakeUIAttrib(m_attrib, &Attribute::fontStyle, L"iFontStyle", + [](Type::SetData param) + { + auto style = CtrlMgr::ConvertAttribPtr(param.attribValue); + if (!style) return false; + + const auto range = std::make_pair(0u, 0u); + const auto _this = param.param; + + if (style->fontCustom != param.data->fontStyle.fontCustom) + _this->m_fontDef = _this->m_render->CreateFonts(L"", style->font.cstr(), style->fontSize, style->fontCustom); + else + { + _this->m_fontDef->SetText(L""); + _this->m_fontDef->SetFontName(style->font.cstr()); + _this->m_fontDef->SetFontSize(style->fontSize, range); + } + _this->m_fontDef->SetFontStyle(style->fontStyle, range); + + param.data->fontStyle = *style; + return true; + }, + [](Type::GetData param) + { + return CtrlMgr::ConvertAttribPtr(¶m.data->fontStyle); + }), + MakeUIAttribEx(m_attrib, &ItemFont::font, &Attribute::fontStyle, L"iFont", + [](Type::SetData param) + { + auto font = param.GetValue(); + if (param.data->fontStyle.font == font) return true; + + param.data->fontStyle.font = font; + param.param->m_fontDef->SetFontName(font); + + return true; + }), + MakeUIAttribEx(m_attrib, &ItemFont::fontSize, &Attribute::fontStyle, L"iFontSize", + [](Type::SetData param) + { + auto fontSize = param.GetValue<_m_ushort>(); + if (param.data->fontStyle.fontSize == fontSize) return true; + + param.data->fontStyle.fontSize = fontSize; + //dpi + auto scale = param.param->GetRectScale().scale(); + fontSize = (_m_ushort)(M_MIN(scale.cx, scale.cy) * (float)fontSize); + + param.param->m_fontDef->SetFontSize(fontSize, std::make_pair(0u, (_m_uint)param.param->m_fontDef->GetText().length())); + + return true; + }), + Type::MakeAttribAtOffset(&UIFontStyle::bold, fontStyleOffset, L"iFontBold", + [](Type::SetData param) + { + bool bold = param.GetValue(); + if (param.data->fontStyle.fontStyle.bold == bold) return true; + + param.data->fontStyle.fontStyle.bold = bold; + param.param->updateStyle(param.data->fontStyle.fontStyle); + return true; + }), + Type::MakeAttribAtOffset(&UIFontStyle::italics, fontStyleOffset, L"iFontItalics", + [](Type::SetData param) + { + bool italics = param.GetValue(); + if (param.data->fontStyle.fontStyle.italics == italics) return true; + + param.data->fontStyle.fontStyle.italics = italics; + param.param->updateStyle(param.data->fontStyle.fontStyle); + return true; + }), + Type::MakeAttribAtOffset(&UIFontStyle::underline, fontStyleOffset, L"iFontUnderline", + [](Type::SetData param) + { + bool underline = param.GetValue(); + if (param.data->fontStyle.fontStyle.underline == underline) return true; + + param.data->fontStyle.fontStyle.underline = underline; + param.param->updateStyle(param.data->fontStyle.fontStyle); + return true; + }), + Type::MakeAttribAtOffset(&UIFontStyle::strikeout, fontStyleOffset, L"iFontStrikeout", + [](Type::SetData param) + { + bool strikeout = param.GetValue(); + if (param.data->fontStyle.fontStyle.strikeout == strikeout) return true; + + param.data->fontStyle.fontStyle.strikeout = strikeout; + param.param->updateStyle(param.data->fontStyle.fontStyle); + return true; + }), + MakeUIAttribEx(m_attrib, &ItemFont::fontColor, &Attribute::fontStyle, L"iFontColor"), + MakeUIAttribEx(m_attrib, &ItemFont::textAlign, &Attribute::fontStyle, L"iTextAlign"), + MakeUIAttribEx(m_attrib, &ItemFont::fontCustom, &Attribute::fontStyle, L"iFontCustom", + [](Type::SetData param) + { + auto fontc = param.GetValue<_m_ptrv>(); + if (param.data->fontStyle.fontCustom == fontc) return true; + + param.data->fontStyle.fontCustom = fontc; + + auto _this = param.param; + auto scale = _this->GetRectScale().scale(); + _m_uint fontSize = (_m_uint)(M_MIN(scale.cx, scale.cy) * (float)param.data->fontStyle.fontSize); + _this->m_fontDef = _this->m_render->CreateFonts(L"", param.data->fontStyle.font.cstr(), fontSize, fontc); + _this->m_fontDef->SetFontStyle(param.data->fontStyle.fontStyle, std::make_pair(0u, 0u)); + + return true; + }), + MakeUIAttrib(m_attrib, &Attribute::drawOffset, L"drawOffset") + }; + + Type::RegisterAttrib(list); + } + + void UIListBox::updateStyle(UIFontStyle& style) + { + m_fontDef->SetFontStyle(style, std::make_pair(0, (_m_uint)m_fontDef->GetText().length())); + } +#pragma endregion +} \ No newline at end of file diff --git a/MiaoUI/src/source/Control/Mui_NavBar.cpp b/MiaoUI/src/source/Control/Mui_NavBar.cpp new file mode 100644 index 0000000..46ca9b5 --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_NavBar.cpp @@ -0,0 +1,674 @@ +/** + * FileName: Mui_NavBar.cpp + * Note: UI导航栏声明 + * + * Copyright (C) 2022-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-9-7 Create +*/ +#include + +namespace Mui::Ctrl +{ + using namespace Helper; + + void UINavBar::Register() + { + BindAttribute(); + + static auto method = [](UIControl* parent) + { + return new UINavBar(parent, Attribute()); + }; + MCTRL_REGISTER(method); + } + + UINavBar::UINavBar(UIControl* parent, Attribute attrib) : UINavBar(std::move(attrib)) + { + M_ASSERT(parent) + parent->AddChildren(this); + } + + UINavBar::UINavBar(Attribute attrib) : m_attrib(std::move(attrib)) + { + m_cacheSupport = true; + UINodeBase::m_data.AutoSize = true; + } + + void UINavBar::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (!m_attrib.SetAttribute(attribName, attrib, { this, draw })) + UIControl::SetAttribute(attribName, attrib, draw); + else + m_cacheUpdate = true; + } + + std::wstring UINavBar::GetAttribute(std::wstring_view attribName) + { + if (std::wstring ret; m_attrib.GetAttribute(attribName, ret)) + return ret; + return UIControl::GetAttribute(attribName); + } + + void UINavBar::SetFontStyle(UILabel::Attribute& attrib, bool draw) + { + m_attrib.SetAttribute(L"fontStyle", &attrib, { this, draw }); + } + + int UINavBar::AddItem(std::wstring_view title, int index, bool draw) + { + if (index == -1) + { + m_itemList.push_back(InitItemRect(title, index)); + if (draw) + UpdateDisplay(); + m_cacheUpdate = true; + } + else if (IndexCheck(index)) + { + m_font->SetText(title.data()); + m_itemList.insert(m_itemList.begin() + index, InitItemRect(title, index)); + //列表发生改变 需要更新当前选中项目的索引 + if (index < m_curSelIndex) + m_curSelIndex++; + if (draw) + UpdateDisplay(); + m_cacheUpdate = true; + } + else + { + _M_OutErrorDbg_(L"AddItem failed!", false); + } + return (int)m_itemList.size(); + } + + void UINavBar::SetItemTitle(int index, std::wstring_view title, bool draw) + { + if (auto _size = m_itemList.size(); _size != 0) + { + if (index == -1) + index = int(_size - 1); + if (IndexCheck(index)) + { + m_itemList[index] = std::make_pair((std::wstring)title, UIRect()); + CalcItemRect(); + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + } + } + + std::wstring UINavBar::GetItemTitle(int index) const + { + if (index == -1 && !m_itemList.empty()) + { + return m_itemList.back().first; + } + if (IndexCheck(index)) + { + return m_itemList[index].first; + } + return L""; + } + + bool UINavBar::SetCurSelItem(int index, bool draw) + { + if (IndexCheck(index)) + { + m_curSelIndex = index; + auto& item = m_itemList[index]; + m_baroffset = item.second.left; + m_baroffset = item.second.GetWidth(); + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + return true; + } + return false; + } + + int UINavBar::GetCurSelItem() const + { + return m_curSelIndex; + } + + int UINavBar::GetItemListCount() const + { + return (int)m_itemList.size(); + } + + int UINavBar::DeleteItem(int index, bool draw) + { + if (!m_itemList.empty()) + { + if (index == -1) + { + if (int(m_itemList.size() - 1) == m_curSelIndex) + m_curSelIndex = 0; + m_itemList.pop_back(); + } + else if (IndexCheck(index)) + { + if (index == m_curSelIndex) + m_curSelIndex = 0; + m_itemList.erase(m_itemList.begin() + index); + CalcItemRect(); + } + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + return (int)m_itemList.size(); + } + + void UINavBar::DeleteAllItem(bool draw) + { + if (!m_itemList.empty()) + { + m_curSelIndex = 0; + m_itemList.clear(); + m_cacheUpdate = true; + if (draw) UpdateDisplay(); + } + } + + void UINavBar::OnLoadResource(MRenderCmd* render, bool recreate) + { + UINodeBase::OnLoadResource(render, recreate); + + auto& attrib = m_attrib.Get(); + + m_effect = nullptr; + + auto scale = GetRectScale().scale(); + const float fontSize = M_MIN(scale.cx, scale.cy) * (float)attrib.fontSize; + + m_font = render->CreateFonts(L"", attrib.font.view(), (_m_uint)fontSize, attrib.fontCustom); + m_brush = render->CreateBrush(attrib.fontColor); + m_font->SetFontStyle(attrib.fontStyle, std::make_pair(0u, 0u)); + } + + void UINavBar::OnPaintProc(MPCPaintParam param) + { + auto& attrib = m_attrib.Get(); + //尺寸更改 重新计算 + if (m_size != UINodeBase::m_data.Size) + { + m_size = UINodeBase::m_data.Size; + CalcItemRect(); + } + if (attrib.shadowUse && !attrib.shadowLow) + { + if (!m_effect) + m_effect = m_render->CreateEffects(MEffects::GaussianBlur, attrib.shadowBlur); + else + m_effect->SetEffectValue(attrib.shadowBlur); + } + + m_brush->SetOpacity(param->cacheCanvas ? 255 : UINodeBase::m_data.AlphaDst); + int index = 0; + for (auto& item : m_itemList) + { + m_font->SetText(item.first); + UIRect dst = item.second; + dst.Offset(param->destRect->left, param->destRect->top); + + _m_scale scale = GetRectScale().scale(); + + //绘制字体阴影 + if (attrib.shadowUse) + { + const UIPoint offset{ _scale_to(attrib.shadowOffset.x, scale.cx), _scale_to(attrib.shadowOffset.y, scale.cy) }; + m_brush->SetColor(attrib.shadowColor); + UIRect offsetrc = dst; + offsetrc.Offset(offset.x, offset.y); + param->render->DrawTextLayout(m_font, offsetrc, m_brush, TextAlign_Left); + } + + //项目标题状态 + if (m_curHoverIndex == index && m_state != UIControlStatus_Normal) + { + if (m_state == UIControlStatus_Hover) + m_brush->SetColor(attrib.fontHoverColor); + else if (m_state == UIControlStatus_Pressed) + m_brush->SetColor(attrib.fontPressColor); + else + m_brush->SetColor(attrib.fontColor); + } + else if (m_curSelIndex == index) + m_brush->SetColor(attrib.fontHoverColor); + else + m_brush->SetColor(attrib.fontColor); + + param->render->DrawTextLayout(m_font, dst, m_brush, TextAlign_Left); + + //绘制横条 + if (m_curSelIndex == index && attrib.barHeight > 0) + { + dst = UIRect(param->destRect->left + m_baroffset, dst.bottom + _scale_to(attrib.barSpace, scale.cy), + m_barwidth, _scale_to(attrib.barHeight, scale.cy)); + + m_brush->SetColor(attrib.barColor); + + if (attrib.barRound != 0.f) + param->render->FillRoundedRect(dst, M_MAX(scale.cx, scale.cy) * (float)attrib.barRound, m_brush); + else + param->render->FillRectangle(dst, m_brush); + } + + index++; + } + } + + bool UINavBar::OnMouseEntered(_m_uint flag, const UIPoint& point) + { + if (IsHitItem(point)) + { + m_state = UIControlStatus_Hover; + m_cacheUpdate = true; + UpdateDisplay(); + } + return UIControl::OnMouseEntered(flag, point); + } + + bool UINavBar::OnMouseExited(_m_uint flag, const UIPoint& point) + { + if (m_curHoverIndex != -1) + { + m_curHoverIndex = -1; + m_state = UIControlStatus_Normal; + m_cacheUpdate = true; + m_down = false; + UpdateDisplay(); + } + return UIControl::OnMouseExited(flag, point); + } + + bool UINavBar::OnLButtonDown(_m_uint flag, const UIPoint& point) + { + m_down = true; + if (m_curHoverIndex != -1) + { + m_state = UIControlStatus_Pressed; + m_cacheUpdate = true; + UpdateDisplay(); + } + return UIControl::OnLButtonDown(flag, point); + } + + bool UINavBar::OnLButtonUp(_m_uint flag, const UIPoint& point) + { + if (!m_down) + return UIControl::OnLButtonUp(flag, point); + + m_down = false; + + if (!IsHitItem(point)) + return UIControl::OnLButtonUp(flag, point); + + if (m_curSelIndex != m_curHoverIndex) + { + //项目横条动画过渡 + auto& beginItem = m_itemList[m_curSelIndex]; + auto& endItem = m_itemList[m_curHoverIndex]; + + int end_left = endItem.second.left; + int begin_width = beginItem.second.GetWidth(); + int end_width = endItem.second.GetWidth(); + + if (begin_width == end_width) + m_barwidth = begin_width; + + m_baroffset = end_left; + m_barwidth = end_width; + + m_curSelIndex = m_curHoverIndex; + SendEvent(Event_NavBar_ItemChange, m_curSelIndex); + } + m_state = UIControlStatus_Hover; + m_cacheUpdate = true; + UpdateDisplay(); + return SendEvent(Event_Mouse_LClick, (_m_param)&point); + } + + bool UINavBar::OnMouseMove(_m_uint flag, const UIPoint& point) + { + if (IsHitItem(point) && m_curHoverIndex != m_lastHover) + { + m_lastHover = m_curHoverIndex; + m_state = UIControlStatus_Hover; + m_cacheUpdate = true; + UpdateDisplay(); + } + else + { + if (m_curHoverIndex != m_lastHover) + { + m_lastHover = -1; + m_cacheUpdate = true; + UpdateDisplay(); + } + } + return UIControl::OnMouseMove(flag, point); + } + + void UINavBar::OnScale(_m_scale scale) + { + UIControl::OnScale(scale); + auto& attrib = m_attrib.Get(); + + scale = GetRectScale().scale(); + const float fontSize = M_MIN(scale.cx, scale.cy) * (float)attrib.fontSize; + if (m_font) + m_font->SetFontSize((_m_uint)fontSize, std::make_pair(0, (_m_uint)m_font->GetText().length())); + CalcItemRect(); + } + + _m_sizef UINavBar::GetContentSize() + { + _m_sizef size = UINodeBase::GetContentSize(); + if (UINodeBase::m_data.AutoSize) + { + CalcItemRect(); + int max_right = 0; + int max_bottom = 0; + for (auto& item : m_itemList) + { + max_right = M_MAX(max_right, item.second.right); + max_bottom = M_MAX(max_bottom, item.second.bottom); + } + auto scale = GetRectScale().scale(); + auto& attrib = m_attrib.Get(); + max_bottom += _scale_to(attrib.barSpace, scale.cy); + max_bottom += _scale_to(attrib.barHeight, scale.cy); + return { (float)max_right, (float)max_bottom }; + } + return size; + } + + void UINavBar::OnLayoutCalced() + { + //CalcItemRect(); + //m_cacheUpdate = true; + //UpdateDisplay(); + } + + bool UINavBar::IndexCheck(int index) const + { + if (index < (int)m_itemList.size() && index >= 0) + return true; + return false; + } + + std::pair UINavBar::InitItemRect(std::wstring_view title, int index) + { + _m_scale scale = GetRectScale().scale(); + int space = _scale_to(m_attrib.Get().itemSpace, scale.cx); + int offset = 0; + if (!m_itemList.empty() && index != 0) + { + if (index == -1) + offset = m_itemList.back().second.right + space; + else + offset = m_itemList[size_t(index)].second.left + space; + } + m_font->SetText(title.data()); + auto ret = std::make_pair((std::wstring)title, m_font->GetMetrics()); + ret.second.left += offset; + ret.second.right += offset + 1; + return ret; + } + + void UINavBar::CalcItemRect() + { + _m_scale scale = GetRectScale().scale(); + int offset = 0; + int index = 0; + for (auto& [text, rect] : m_itemList) + { + //计算项目尺寸 + m_font->SetText(text); + rect = m_font->GetMetrics(); + rect.left += offset; + rect.right += offset + 1; + offset += rect.GetWidth() + _scale_to(m_attrib.Get().itemSpace, scale.cx); + + //横条位置和尺寸 + if (index == m_curSelIndex) + { + m_baroffset = rect.left; + m_barwidth = rect.GetWidth(); + } + + index++; + } + } + + bool UINavBar::IsHitItem(const UIPoint& pt) + { + int index = 0; + for (auto& item : m_itemList) + { + UIRect hover = item.second; + hover.Offset((int)UINodeBase::m_data.Frame.left, (int)UINodeBase::m_data.Frame.top); + if (Rect::IsPtInside(hover, pt)) + { + m_curHoverIndex = index; + return true; + } + hover.ResetOffset(); + index++; + } + m_curHoverIndex = -1; + return false; + } + + void UINavBar::BindAttribute() + { + using Type = decltype(m_attrib); + + auto list = + { + Type::MakeAttrib(&Attribute::fontStyle, L"fontStyle", + [](Type::SetData param) + { + UILabel::Attribute defaults; + auto style = param.GetValue(); + if (!style) style = &defaults; + + const auto range = std::make_pair(0u, (_m_uint)style->text.length()); + const auto _this = param.param.first; + + if (style->fontCustom != param.data->fontCustom) + _this->m_font = _this->m_render->CreateFonts(style->text.cstr(), style->font.cstr(), style->fontSize, style->fontCustom); + else + { + _this->m_font->SetFontName(style->font.cstr()); + _this->m_font->SetFontSize(style->fontSize, range); + } + _this->m_font->SetFontStyle(style->fontStyle, range); + + param.data->font = style->font; + param.data->fontColor = style->fontColor; + param.data->fontSize = style->fontSize; + param.data->fontStyle = style->fontStyle; + param.data->fontCustom = style->fontCustom; + param.data->shadowUse = style->shadowUse; + param.data->shadowLow = style->shadowLow; + param.data->shadowBlur = style->shadowBlur; + param.data->shadowOffset = style->shadowOffset; + param.data->shadowColor = style->shadowColor; + return _this->updateRender(param.param.second, true); + }, + [](Type::GetData param) + { + return CtrlMgr::ConvertAttribPtr(¶m.data->fontStyle); + }), + + MakeUIAttrib(m_attrib, &Attribute::font, L"font", + [](Type::SetData param) + { + if (param.data->font == param.attribValue) + return true; + + param.data->font = param.attribValue; + param.param.first->m_font->SetFontName(param.data->font.cstr()); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttrib(m_attrib, &Attribute::fontColor, L"fontColor"), + MakeUIAttrib(m_attrib, &Attribute::fontHoverColor, L"fontHoverColor"), + MakeUIAttrib(m_attrib, &Attribute::fontPressColor, L"fontPressColor"), + + MakeUIAttrib(m_attrib, &Attribute::fontSize, L"fontSize", + [](Type::SetData param) + { + auto fontSize = param.GetValue<_m_ushort>(); + if (param.data->fontSize == fontSize) + return true; + + param.data->fontSize = fontSize; + const auto _this = param.param.first; + + //dpi + auto scale = _this->GetRectScale().scale(); + fontSize = (_m_ushort)(M_MIN(scale.cx, scale.cy) * (float)fontSize); + param.param.first->m_font->SetFontSize(fontSize, { 0u, (_m_uint)param.param.first->m_font->GetText().length()}); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttribEx(m_attrib, &UIFontStyle::bold, &Attribute::fontStyle, L"fontBold", + [](Type::SetData param) + { + bool value = param.GetValue(); + if (param.data->fontStyle.bold == value) + return true; + + param.data->fontStyle.bold = value; + param.param.first->updateStyle(param.data->fontStyle); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttribEx(m_attrib, &UIFontStyle::italics, &Attribute::fontStyle, L"fontItalics", + [](decltype(m_attrib)::SetData param) + { + bool value = param.GetValue(); + if (param.data->fontStyle.italics == value) + return true; + + param.data->fontStyle.italics = value; + param.param.first->updateStyle(param.data->fontStyle); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttribEx(m_attrib, &UIFontStyle::underline, &Attribute::fontStyle, L"fontUnderline", + [](decltype(m_attrib)::SetData param) + { + bool value = param.GetValue(); + if (param.data->fontStyle.underline == value) + return true; + + param.data->fontStyle.underline = value; + param.param.first->updateStyle(param.data->fontStyle); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttribEx(m_attrib, &UIFontStyle::strikeout, &Attribute::fontStyle, L"fontStrikeout", + [](decltype(m_attrib)::SetData param) + { + bool value = param.GetValue(); + if (param.data->fontStyle.strikeout == value) + return true; + + param.data->fontStyle.strikeout = value; + param.param.first->updateStyle(param.data->fontStyle); + + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttrib(m_attrib, &Attribute::fontCustom, L"fontCustom", + [](decltype(m_attrib)::SetData param) + { + auto fontc = param.GetValue<_m_ptrv>(); + if (param.data->fontCustom == fontc) return true; + + param.data->fontCustom = fontc; + const auto _this = param.param.first; + + auto scale = _this->GetRectScale().scale(); + _m_uint fontSize = (_m_uint)(M_MIN(scale.cx, scale.cy) * (float)param.data->fontSize); + _this->m_font = _this->m_render->CreateFonts(L"", param.data->font.cstr(), fontSize, fontc); + _this->m_font->SetFontStyle(param.data->fontStyle, std::make_pair(0u, 0u)); + + return _this->updateRender(param.param.second, true); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::shadowUse, L"shadowUse", param, + { + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::shadowBlur, L"shadowBlur", param, + { + return param.param.first->updateRender(param.param.second); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::shadowColor, L"shadowColor", param, + { + return param.param.first->updateRender(param.param.second); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::shadowOffset, L"shadowOffset", param, + { + return param.param.first->updateRender(param.param.second, true); + }), + + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::shadowLow, L"shadowLow", param, + { + return param.param.first->updateRender(param.param.second); + }), + + MakeUIAttrib(m_attrib, &Attribute::itemSpace, L"itemSpace"), + MakeUIAttrib(m_attrib, &Attribute::barSpace, L"barSpace"), + MakeUIAttrib(m_attrib, &Attribute::barHeight, L"barHeight"), + MakeUIAttrib(m_attrib, &Attribute::barAnitime, L"barAnitime"), + MakeUIAttrib(m_attrib, &Attribute::barRound, L"barRound"), + MakeUIAttrib(m_attrib, &Attribute::barColor, L"barColor"), + }; + + Type::RegisterAttrib(list); + } + + void UINavBar::updateStyle(UIFontStyle& style) + { + m_font->SetFontStyle(style, std::make_pair(0, (_m_uint)m_font->GetText().length())); + } + + bool UINavBar::updateRender(bool draw, bool layout) + { + m_cacheUpdate = true; + if (!draw) return true; + if (layout) + CalcItemRect(); + UpdateDisplay(); + return true; + } +} \ No newline at end of file diff --git a/MiaoUI/src/source/Control/Mui_Progress.cpp b/MiaoUI/src/source/Control/Mui_Progress.cpp new file mode 100644 index 0000000..068a402 --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_Progress.cpp @@ -0,0 +1,147 @@ +/** + * FileName: Mui_Progress.cpp + * Note: UI进度条控件实现 + * + * Copyright (C) 2020-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-11-24 Create +*/ +#include + +namespace Mui::Ctrl +{ + UIProgressBar::UIProgressBar(UIControl* parent, Attribute attrib) : UIProgressBar(std::move(attrib)) + { + M_ASSERT(parent) + parent->AddChildren(this); + } + + UIProgressBar::UIProgressBar(Attribute attrib) : m_attrib(std::move(attrib)) + { + m_cacheUpdate = true; + } + + void UIProgressBar::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (m_attrib.SetAttribute(attribName, attrib)) + { + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + else + UIControl::SetAttribute(attribName, attrib, draw); + } + + std::wstring UIProgressBar::GetAttribute(std::wstring_view attribName) + { + if (std::wstring ret; m_attrib.GetAttribute(attribName, ret)) + return ret; + return UIControl::GetAttribute(attribName); + } + + void UIProgressBar::Register() + { + BindAttribute(); + + static auto method = [](UIControl* parent) + { + return new UIProgressBar(parent, Attribute()); + }; + MCTRL_REGISTER(method); + } + + void UIProgressBar::SetMaxValue(int value, bool draw) + { + m_attrib.Set().maxValue = value; + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + int UIProgressBar::GetMaxValue() const + { + return m_attrib.Get().maxValue; + } + + void UIProgressBar::SetCurValue(int value, bool draw) + { + m_attrib.Set().value = value; + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + int UIProgressBar::GetCurValue() const + { + return m_attrib.Get().value; + } + + void UIProgressBar::OnPaintProc(MPCPaintParam param) + { + const auto style = m_attrib.Get().style; + if (!style) + return; + CalcDrawRect(param->destRect); + _m_byte alpha = UINodeBase::m_data.AlphaDst; + if (param->cacheCanvas) alpha = 255; + auto scale = GetRectScale().scale(); + style->PaintStyle(param->render, param->destRect, alpha, IsEnabled() ? 0 : 2, 4, scale); + style->PaintStyle(param->render, &m_drawRect, alpha, IsEnabled() ? 1 : 3, 4, scale); + } + + int UIProgressBar::Percentage(int value1, int percentValue, int percent) + { + float ret = float(value1) * (float)percentValue / (float)percent; + return (int)ret; + } + + void UIProgressBar::CalcDrawRect(MPCRect dest) + { + m_drawRect = *dest; + + if (auto& attrib = m_attrib.Get(); attrib.vertical) + { + int calcValue = Percentage(attrib.value, m_drawRect.GetHeight(), attrib.maxValue); + if (attrib.bottomShow) + m_drawRect.top += m_drawRect.GetHeight() - calcValue; + else + m_drawRect.bottom -= m_drawRect.GetHeight() - calcValue; + } + else + { + int calcValue = Percentage(attrib.value, m_drawRect.GetWidth(), attrib.maxValue); + if (!attrib.leftShow) + m_drawRect.left += m_drawRect.GetWidth() - calcValue; + else + m_drawRect.right -= m_drawRect.GetWidth() - calcValue; + } + } + + void UIProgressBar::BindAttribute() + { + auto list = + { + MakeUIAttrib(m_attrib, &Attribute::style, L"style"), + MakeUIAttrib(m_attrib, &Attribute::maxValue, L"maxValue"), + MakeUIAttrib(m_attrib, &Attribute::value, L"value"), + MakeUIAttrib(m_attrib, &Attribute::vertical, L"vertical"), + MakeUIAttrib(m_attrib, &Attribute::leftShow, L"leftShow"), + MakeUIAttrib(m_attrib, &Attribute::bottomShow, L"bottomShow") + }; + + decltype(m_attrib)::RegisterAttrib(list); + } +} diff --git a/MiaoUI/src/source/Control/Mui_Scroll.cpp b/MiaoUI/src/source/Control/Mui_Scroll.cpp new file mode 100644 index 0000000..30e9199 --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_Scroll.cpp @@ -0,0 +1,806 @@ +/** + * FileName: Mui_Scroll.cpp + * Note: UI滚动条控件实现 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-11-15 Create +*/ + +#include + +namespace Mui::Ctrl +{ + using namespace Helper; + + UIScroll::UIScroll(UIControl* parent, Attribute attrib) : UIScroll(std::move(attrib)) + { + M_ASSERT(parent) + parent->AddChildren(this); + } + + UIScroll::UIScroll(Attribute attrib) : m_attrib(std::move(attrib)) + { + m_cacheSupport = true; + } + + int UIScroll::CalcOffsetDragValue(bool horizontal, int dragValue, int offset) + { + int value = dragValue; + int range = GetRange(horizontal); + if (offset > range) + range = offset; + float percentage = (float)value / (float)range; + range -= offset; + value = int((float)range * percentage); + return value; + } + + void UIScroll::Register() + { + BindAttribute(); + + static auto method = [](UIControl* parent) + { + return new UIScroll(parent, Attribute()); + }; + MCTRL_REGISTER(method); + } + + void UIScroll::OnPaintProc(MPCPaintParam param) + { + UISize size = { param->destRect->GetWidth(), param->destRect->GetHeight() }; + if (size.width != m_size.width + || size.height != m_size.height) + { + m_size = size; + CalcControlRect(); + } + //控件是否可用 + _m_byte alpha = UINodeBase::m_data.AlphaDst; + if (param->cacheCanvas) alpha = 255; + auto scale = GetRectScale().scale(); + auto Paint = [&](UIStyle* style, UIRect rc, ScrollButtonType type, bool horizontal) + { + rc.Offset(param->destRect->left, param->destRect->top); + PaintControl(param->render, style, m_btnType == type && m_btnTypeH == horizontal + ? m_status : UIControlStatus_Normal, type, rc, alpha, scale); + }; + auto& attrib = m_attrib.Get(); + //垂直滚动条 + if (attrib.vertical && attrib.styleV) + { + Paint(attrib.styleV.get(), m_scroll[0].track, Track, false); + if (attrib.button) + { + Paint(attrib.styleV.get(), m_scroll[0].scrollBtn[0], TopButton, false); + Paint(attrib.styleV.get(), m_scroll[0].scrollBtn[1], BottomButton, false); + } + Paint(attrib.styleV.get(), m_scroll[0].thumbButton, ThumbButton, false); + } + //水平滚动条 + if (attrib.horizontal && attrib.styleH) + { + Paint(attrib.styleH.get(), m_scroll[1].track, Track, true); + if (attrib.button) + { + Paint(attrib.styleH.get(), m_scroll[1].scrollBtn[0], TopButton, true); + Paint(attrib.styleH.get(), m_scroll[1].scrollBtn[1], BottomButton, true); + } + Paint(attrib.styleH.get(), m_scroll[1].thumbButton, ThumbButton, true); + } + //交汇点 + if (attrib.vertical && attrib.styleV) + { + Paint(attrib.styleV.get(), m_intersect, Intersect, false); + } + } + + int UIScroll::GetRange(bool horizontal) const + { + if(horizontal) + return m_attrib.Get().range.width; + return m_attrib.Get().range.height; + } + + UISize UIScroll::GetRange() const + { + return m_attrib.Get().range; + } + + void UIScroll::SetRange(bool horizontal, int range, bool draw) + { + m_attrib.SetAttribute(horizontal ? L"rangeH" : L"rangeV", range, this); + CalcControlRect(); + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + void UIScroll::SetRange(UISize range, bool draw) + { + m_attrib.SetAttribute(L"range", range, this); + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + int UIScroll::GetDragValue(bool horizontal) const + { + if (horizontal) + return m_attrib.Get().dragValue.width; + return m_attrib.Get().dragValue.height; + } + + UISize UIScroll::GetDragValue() const + { + return m_attrib.Get().dragValue; + } + + void UIScroll::SetDragValue(bool horizontal, int value, bool draw) + { + SetDragValueNoAni(horizontal, value, draw); + } + + void UIScroll::CalcControlRect() + { + auto& attrib = m_attrib.Get(); + int barWidth = attrib.barWidth; + auto inset = attrib.inset; + int btnHeight = attrib.btnHeight; + + //dpi + auto [ws, hs] = GetRectScale().scale(); + float s = M_MIN(ws, hs); + barWidth = int(s * (float)barWidth); + btnHeight = int(s * (float)btnHeight); + inset.left = _scale_to(inset.left, ws); + inset.right = _scale_to(inset.right, ws); + inset.top = _scale_to(inset.top, hs); + inset.bottom = _scale_to(inset.bottom, hs); + + UIRect controlRc = UIRect(0, 0, (int)UINodeBase::m_data.Frame.GetWidth(), (int)UINodeBase::m_data.Frame.GetHeight()); + + //垂直滚动条 + if (attrib.vertical) + { + UIRect VscrollRc = controlRc; + + VscrollRc.left = VscrollRc.right - barWidth - inset.right; + VscrollRc.top += inset.top; + VscrollRc.right -= inset.right; + VscrollRc.bottom -= inset.bottom; + + //如果有水平滚动条 计算出底部交汇区域 + if (attrib.horizontal) + { + VscrollRc.bottom -= barWidth;//减去水平滚动条的宽度(高度) + + m_intersect.left = VscrollRc.left; + m_intersect.top = VscrollRc.bottom + 1; + m_intersect.right = VscrollRc.right; + m_intersect.bottom = controlRc.bottom - inset.bottom; + } + + UIRect Track = VscrollRc; + + //计算轨道和滚动调节按钮区域 + if (attrib.button) + { + UIRect topBtn, BottomBtn; + topBtn = BottomBtn = Track; + topBtn.bottom = topBtn.top + btnHeight; + + BottomBtn.top = BottomBtn.bottom - btnHeight; + + Track.top = topBtn.bottom + 1; + Track.bottom = BottomBtn.top - 1; + + m_scroll[0].scrollBtn[0] = topBtn; + m_scroll[0].scrollBtn[1] = BottomBtn; + } + m_scroll[0].track = Track; + + UIRect Thumb = Track; + + //计算拖拽按钮矩形 + int TrackH = m_scroll[0].track.GetHeight(); + if (attrib.range.height > TrackH) + { + //计算滑块按钮高度 + float viewRatio = (float)TrackH / (float)attrib.range.height; + int _btnHeight = int((float)TrackH * viewRatio); + if (_btnHeight < attrib.barMinHeight) + _btnHeight = attrib.barMinHeight; + Thumb.bottom = Thumb.top + _btnHeight; + } + m_scroll[0].thumbButton = Thumb; + CalcThumbBtnPos(false); + } + //水平滚动条 + if (attrib.horizontal) + { + UIRect HscrollRc = controlRc; + + HscrollRc.left += inset.left; + HscrollRc.top = HscrollRc.bottom - inset.bottom - barWidth; + HscrollRc.bottom -= inset.bottom; + if (attrib.vertical) + HscrollRc.right = m_intersect.left - 1; + else + HscrollRc.right -= inset.right; + + UIRect Track = HscrollRc; + + if (attrib.button) + { + UIRect leftBtn, RightBtn; + leftBtn = RightBtn = Track; + leftBtn.right = leftBtn.left + btnHeight; + + RightBtn.left = RightBtn.right - btnHeight; + + Track.left = leftBtn.right + 1; + Track.right = RightBtn.left - 1; + + m_scroll[1].scrollBtn[0] = leftBtn; + m_scroll[1].scrollBtn[1] = RightBtn; + } + m_scroll[1].track = Track; + + UIRect Thumb = Track; + + //计算拖拽按钮矩形 + int TrackW = m_scroll[1].track.GetWidth(); + if (attrib.range.width > TrackW) + { + //计算滑块按钮宽度 + float viewRatio = (float)TrackW / (float)attrib.range.width; + int _btnWidth = int((float)TrackW * viewRatio); + if (_btnWidth < attrib.barMinHeight) + _btnWidth = attrib.barMinHeight; + Thumb.right = Thumb.left + _btnWidth; + } + m_scroll[1].thumbButton = Thumb; + CalcThumbBtnPos(true); + } + } + + void UIScroll::CalcThumbBtnPos(bool Horizontal) + { + auto& attrib = m_attrib.Get(); + if (!Horizontal) + { + float TrackValue = (float)m_scroll[0].track.GetHeight(); + float ThumbBtnH = (float)m_scroll[0].thumbButton.GetHeight(); + TrackValue -= ThumbBtnH; + if (TrackValue != 0.f && attrib.dragValue.height <= attrib.range.height) + { + //根据Drag值计算占用空间 + TrackValue /= 100.f;//化成小数运算 不然要用long long + TrackValue = (float)attrib.dragValue.height * TrackValue / (float)attrib.range.height; + UIRect btnRc = m_scroll[0].thumbButton; + btnRc.top = m_scroll[0].track.top + int(TrackValue * 100.f); + btnRc.bottom = btnRc.top + (int)ThumbBtnH; + m_scroll[0].thumbButton = btnRc; + } + else + m_scroll[0].thumbButton = m_scroll[0].track; + } + else + { + float TrackValue = (float)m_scroll[1].track.GetWidth(); + float ThumbBtnW = (float)m_scroll[1].thumbButton.GetWidth(); + TrackValue -= ThumbBtnW; + if (TrackValue != 0.f && attrib.dragValue.width <= attrib.range.width) + { + TrackValue /= 100.f; + TrackValue = (float)attrib.dragValue.width * TrackValue / (float)attrib.range.width; + UIRect btnRc = m_scroll[1].thumbButton; + btnRc.left = m_scroll[1].track.left + int(TrackValue * 100.f); + btnRc.right = btnRc.left + (int)ThumbBtnW; + m_scroll[1].thumbButton = btnRc; + } + else + m_scroll[1].thumbButton = m_scroll[1].track; + } + } + + void UIScroll::CalcDragValue(bool horizontal) + { + auto& data = m_attrib.Set(); + if (!horizontal) + { + //根据裁剪按钮空间反推Drag值 + int TrackValue = m_scroll[0].thumbButton.top - m_scroll[0].track.top; + int height = m_scroll[0].track.GetHeight() - m_scroll[0].thumbButton.GetHeight(); + float value = 0; + if(TrackValue != 0) + value = (float)TrackValue / (float)height * (float)data.range.height; + data.dragValue.height = (int)round(value); + } + else + { + int TrackValue = m_scroll[1].thumbButton.left - m_scroll[1].track.left; + int width = m_scroll[1].track.GetWidth() - m_scroll[1].thumbButton.GetWidth(); + float value = 0; + if(TrackValue != 0) + value = (float)TrackValue / (float)width * (float)data.range.width; + data.dragValue.width = (int)round(value); + } + } + + void UIScroll::PaintControl(MRenderCmd* render, UIStyle* style, UIControlStatus status, + ScrollButtonType type, UIRect& drawRect, _m_byte& alpha, _m_scale scale) + { + if (!IsEnabled()) + status = UIControlStatus_Disable; + + int rectCout = -1; + switch (type) + { + //按钮有4种状态 依次排列+4 + case TopButton: + rectCout = (int)status; + break; + case BottomButton: + rectCout = (int)status + 4; + break; + case ThumbButton: + { + rectCout = (int)status + 8; + break; + } + //轨道和交汇点的皮肤只有2种状态 普通和禁用 + case Track: + if (status != UIControlStatus_Disable) + status = UIControlStatus_Normal; + else + status = UIControlStatus_Hover;//Disable + rectCout = (int)status + 12; + break; + case Intersect: + if (status != UIControlStatus_Disable) + status = UIControlStatus_Normal; + else + status = UIControlStatus_Hover;//Disable + rectCout = (int)status + 14; + break; + case ButtonNull: + break; + } + + if (rectCout != -1 && style->GetPartCount() == 16) + style->PaintStyle(render, &drawRect, alpha, rectCout, 16, scale); + } + + void UIScroll::SetDragValueNoAni(bool horizontal, int value, bool draw) + { + if (horizontal) + m_attrib.Set().dragValue.width = value; + else + m_attrib.Set().dragValue.height = value; + CalcThumbBtnPos(horizontal); + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + int UIScroll::GetScrollPage(bool up, bool horz, float count) + { + auto& attrib = m_attrib.Get(); + const int value = horz ? attrib.dragValue.width : attrib.dragValue.height; + const int range = horz ? attrib.range.width : attrib.range.height; + int pageSize; + if (horz) + { + pageSize = int(float(UINodeBase::m_data.Frame.GetWidth()) * count); + if (!up) + pageSize += value; + else + pageSize = value - pageSize; + } + else + { + pageSize = int(float(UINodeBase::m_data.Frame.GetHeight()) * count); + if (!up) + pageSize += value; + else + pageSize = value - pageSize; + } + if (pageSize < 0) + pageSize = 0; + else if (pageSize > range) + pageSize = range; + + return pageSize; + } + + void UIScroll::BindAttribute() + { + using Type = decltype(m_attrib); + + auto list = + { + MakeUIAttrib(m_attrib, &Attribute::styleV, L"styleV"), + MakeUIAttrib(m_attrib, &Attribute::styleH, L"styleH"), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::vertical, L"vertical", param, + { + param.param->CalcControlRect(); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::horizontal, L"horizontal", param, + { + param.param->CalcControlRect(); + return true; + }), + MakeUIAttrib(m_attrib, &Attribute::active, L"active"), + MakeUIAttrib(m_attrib, &Attribute::animate, L"animate"), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::button, L"button", param, + { + param.param->CalcControlRect(); + return true; + }), + MakeUIAttrib(m_attrib, &Attribute::range, L"range", + [](Type::SetData param) + { + param.Assign(&Attribute::range); + if (param.data->range.width < param.data->dragValue.width) + param.data->dragValue.width = param.data->range.width; + if (param.data->range.height < param.data->dragValue.height) + param.data->dragValue.height = param.data->range.height; + param.param->CalcControlRect(); + return true; + }), + MakeUIAttribEx(m_attrib, &UISize::height, &Attribute::range, L"rangeV", + [](Type::SetData param) + { + param.data->range.height = param.GetValue(); + if (param.data->range.height < param.data->dragValue.height) + param.data->dragValue.height = param.data->range.height; + param.param->CalcControlRect(); + return true; + }), + MakeUIAttribEx(m_attrib, &UISize::width, &Attribute::range, L"rangeH", + [](Type::SetData param) + { + param.data->range.width = param.GetValue(); + if (param.data->range.width < param.data->dragValue.width) + param.data->dragValue.width = param.data->range.width; + param.param->CalcControlRect(); + return true; + }), + MakeUIAttrib(m_attrib, &Attribute::dragValue, L"dragValue", + [](Type::SetData param) + { + if (param.data->range.width < param.data->dragValue.width) + param.data->dragValue.width = param.data->range.width; + if (param.data->range.height < param.data->dragValue.height) + param.data->dragValue.height = param.data->range.height; + param.param->CalcThumbBtnPos(false); + param.param->CalcThumbBtnPos(true); + return true; + }), + MakeUIAttribEx(m_attrib, &UISize::height, &Attribute::dragValue, L"dragValueV", + [](Type::SetData param) + { + param.data->dragValue.height = param.GetValue(); + if (param.data->range.height < param.data->dragValue.height) + param.data->dragValue.height = param.data->range.height; + param.param->CalcThumbBtnPos(false); + return true; + }), + MakeUIAttribEx(m_attrib, &UISize::width, &Attribute::dragValue, L"dragValueH", + [](Type::SetData param) + { + param.data->dragValue.width = param.GetValue(); + if (param.data->range.width < param.data->dragValue.width) + param.data->dragValue.width = param.data->range.width; + param.param->CalcThumbBtnPos(true); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::barWidth, L"barWidth", param, + { + param.param->CalcControlRect(); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::barMinHeight, L"barMinHeight", param, + { + param.param->CalcControlRect(); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::btnHeight, L"btnHeight", param, + { + param.param->CalcControlRect(); + return true; + }), + MakeUIAttrib_AfterSetOnly(m_attrib, &Attribute::inset, L"inset", param, + { + param.param->CalcControlRect(); + return true; + }), + }; + + Type::RegisterAttrib(list); + } + + UIScroll::ScrollButtonType UIScroll::CalcPointIn(const UIPoint& pt) + { + UIPoint offset = pt; + offset.x -= (int)UINodeBase::m_data.Frame.left; + offset.y -= (int)UINodeBase::m_data.Frame.top; + + bool TypeH = false; + auto ret = ButtonNull; + auto& attrib = m_attrib.Get(); + if (attrib.vertical) + { + if (Rect::IsPtInside(m_scroll[0].thumbButton, offset)) + { + ret = ThumbButton; + TypeH = false; + goto End; + } + if (attrib.button) + { + if (Rect::IsPtInside(m_scroll[0].scrollBtn[0], offset)) + { + ret = TopButton; + TypeH = false; + goto End; + } + if (Rect::IsPtInside(m_scroll[0].scrollBtn[1], offset)) + { + ret = BottomButton; + TypeH = false; + goto End; + } + } + if (Rect::IsPtInside(m_scroll[0].track, offset)) + { + ret = Track; + TypeH = false; + goto End; + } + } + if (attrib.horizontal) + { + if (Rect::IsPtInside(m_scroll[1].thumbButton, offset)) + { + ret = ThumbButton; + TypeH = true; + goto End; + } + if (attrib.button) + { + if (Rect::IsPtInside(m_scroll[1].scrollBtn[0], offset)) + { + ret = TopButton; + TypeH = true; + goto End; + } + if (Rect::IsPtInside(m_scroll[1].scrollBtn[1], offset)) + { + ret = BottomButton; + TypeH = true; + goto End; + } + } + if (Rect::IsPtInside(m_scroll[1].track, offset)) + { + ret = Track; + TypeH = true; + } + } + + End: + if (m_btnTypeH != TypeH && !isCapture()) + { + m_isClick = TypeH;//已经改变了位置 按下状态无效 + m_btnTypeH = TypeH; + } + + return ret; + } + + bool UIScroll::OnMouseMove(_m_uint flag, const UIPoint& point) + { + auto type = CalcPointIn(point); + if (m_btnType != type && !isCapture()) + { + m_btnType = type; + m_isClick = false; + m_status = UIControlStatus_Hover; + m_cacheUpdate = true; + UpdateDisplay(); + } //拖动滑块 + else if (auto& attrib = m_attrib.Get(); m_btnType == ThumbButton && m_isClick) + { + UIPoint offset = { (int)UINodeBase::m_data.Frame.left, (int)UINodeBase::m_data.Frame.top }; + + if (!m_btnTypeH) + { + int height = m_scroll[0].thumbButton.GetHeight(); + int top = point.y - offset.y;//让按钮顶边等于鼠标点击的位置 + top -= m_clickPos.y;//减去点击位置的距离 + + //不能拖动超过滚动条轨道范围 + if (top < m_scroll[0].track.top) + top = m_scroll[0].track.top; + int bottom = top + height; + if (bottom > m_scroll[0].track.bottom) + { + bottom = m_scroll[0].track.bottom; + top = bottom - height; + } + m_scroll[0].thumbButton.top = top; + m_scroll[0].thumbButton.bottom = bottom; + + CalcDragValue(false); + if (attrib.callback) + attrib.callback(this, attrib.dragValue.height, false); + } + else + { + int width = m_scroll[1].thumbButton.GetWidth(); + int left = point.x - offset.x; + left -= m_clickPos.x; + + //不能拖动超过滚动条轨道范围 + if (left < m_scroll[1].track.left) + left = m_scroll[1].track.left; + int right = left + width; + if (right > m_scroll[1].track.right) + { + right = m_scroll[1].track.right; + left = right - width; + } + m_scroll[1].thumbButton.left = left; + m_scroll[1].thumbButton.right = right; + + CalcDragValue(true); + if (attrib.callback) + attrib.callback(this, attrib.dragValue.width, true); + } + m_cacheUpdate = true; + UpdateDisplay(); + } + SendEvent(Event_Mouse_Move, (_m_param)&point); + return m_btnType != ButtonNull; + } + + bool UIScroll::OnMouseExited(_m_uint flag, const UIPoint& point) + { + if (!isCapture()) + { + m_status = UIControlStatus_Normal; + m_btnType = ButtonNull; + m_cacheUpdate = true; + UpdateDisplay(); + } + SendEvent(Event_Mouse_Exited, (_m_param)&point); + return false; + } + + bool UIScroll::OnLButtonDown(_m_uint flag, const UIPoint& point) + { + auto type = CalcPointIn(point); + m_btnType = type; + m_isClick = true; + m_status = UIControlStatus_Pressed; + if (type != ButtonNull) + { + if (type == ThumbButton) + { + SetCapture(); + UIPoint offset = { (int)UINodeBase::m_data.Frame.left, (int)UINodeBase::m_data.Frame.top }; + offset.x = point.x - offset.x; + offset.y = point.y - offset.y; + //计算出鼠标点击位置到按钮顶边的距离 + offset.y -= m_scroll[0].thumbButton.top; + offset.x -= m_scroll[1].thumbButton.left; + m_clickPos = offset; + } + m_cacheUpdate = true; + UpdateDisplay(); + } + SendEvent(Event_Mouse_LDown, (_m_param)&point); + return m_btnType != ButtonNull; + } + + bool UIScroll::OnLButtonUp(_m_uint flag, const UIPoint& point) + { + auto type = CalcPointIn(point); + m_btnType = type; + m_status = UIControlStatus_Hover; + if (auto& attrib = m_attrib.Get(); m_isClick) + { + if (type != ButtonNull && (type == TopButton || type == BottomButton || type == Track)) + { + //每次递增或递减半个page的内容 + int value = m_btnTypeH ? attrib.dragValue.width : attrib.dragValue.height; + if (type == TopButton) + { + value = GetScrollPage(true, m_btnTypeH, 0.5f); + } + else if (type == BottomButton) + { + value = GetScrollPage(false, m_btnTypeH, 0.5f); + } + //每次递增或递减一个page的内容 + else if (type == Track) + { + UIPoint offset = point; + if (m_btnTypeH) + { + offset.x -= (int)UINodeBase::m_data.Frame.left; + value = GetScrollPage(offset.x < m_scroll[1].thumbButton.right, true, 1.f); + } + else + { + offset.y -= (int)UINodeBase::m_data.Frame.top; + value = GetScrollPage(offset.y < m_scroll[0].thumbButton.bottom, false, 1.f); + } + } + SetDragValue(m_btnTypeH, value, true); + if (attrib.callback) + attrib.callback(this, m_btnTypeH ? attrib.dragValue.width : attrib.dragValue.height, m_btnTypeH); + } + ReleaseCapture(); + m_isClick = false; + } + SendEvent(Event_Mouse_LUp, (_m_param)&point); + return m_btnType != ButtonNull; + } + + bool UIScroll::OnMouseWheel(_m_uint flag, short delta, const UIPoint& point) + { + if (m_btnType != ButtonNull || m_isClick || m_ALLWheel) + { + auto& attrib = m_attrib.Get(); + //每次递增或递减1/2的内容 + int value; + if (delta > 0) + value = GetScrollPage(true, m_btnTypeH, 0.5f * 1.f); + else + value = GetScrollPage(false, m_btnTypeH, 0.5f * 1.f); + + SetDragValue(m_btnTypeH, value, true); + if (attrib.callback) + attrib.callback(this, m_btnTypeH ? attrib.dragValue.width : attrib.dragValue.height, m_btnTypeH); + } + UIControl::OnMouseWheel(flag, delta, point); + return m_btnType != ButtonNull; + } + + void UIScroll::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (m_attrib.SetAttribute(attribName, attrib, this)) + { + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + else + UIControl::SetAttribute(attribName, attrib, draw); + } + + std::wstring UIScroll::GetAttribute(std::wstring_view attribName) + { + if (std::wstring ret; m_attrib.GetAttribute(attribName, ret)) + return ret; + return UIControl::GetAttribute(attribName); + } +} diff --git a/MiaoUI/src/source/Control/Mui_Slider.cpp b/MiaoUI/src/source/Control/Mui_Slider.cpp new file mode 100644 index 0000000..a9a0e1e --- /dev/null +++ b/MiaoUI/src/source/Control/Mui_Slider.cpp @@ -0,0 +1,377 @@ +/** + * FileName: Mui_Slider.cpp + * Note: UI滑块条控件实现 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-12-20 Create +*/ + +#include + +namespace Mui::Ctrl +{ + UISlider::UISlider(UIControl* parent, Attribute attrib) : UISlider(std::move(attrib)) + { + M_ASSERT(parent) + parent->AddChildren(this); + } + + UISlider::UISlider(Attribute attrib) : m_attrib(std::move(attrib)) + { + m_cacheSupport = true; + } + + void UISlider::SetAttribute(std::wstring_view attribName, std::wstring_view attrib, bool draw) + { + if (m_attrib.SetAttribute(attribName, attrib)) + { + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + else + UIControl::SetAttribute(attribName, attrib, draw); + } + + std::wstring UISlider::GetAttribute(std::wstring_view attribName) + { + if (std::wstring ret; m_attrib.GetAttribute(attribName, ret)) + return ret; + return UIControl::GetAttribute(attribName); + } + + void UISlider::Register() + { + BindAttribute(); + + static auto method = [](UIControl* parent) + { + return new UISlider(parent, Attribute()); + }; + MCTRL_REGISTER(method); + } + + void UISlider::SetCurValue(int value, bool draw) + { + auto ptr = m_attrib.GetPtr(); + if (value > ptr->maxValue) + value = ptr->maxValue; + if (value < 0) + value = 0; + ptr->value = value; + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + int UISlider::GetCurValue() const + { + return m_attrib.Get().value; + } + + void UISlider::SetMaxValue(int max, bool draw) + { + if (max < 0) + max = 0; + m_attrib.Set().maxValue = max; + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + int UISlider::GetMaxValue() const + { + return m_attrib.Get().maxValue; + } + + void UISlider::SetMinValue(int min, bool draw) + { + if (min < 0) + min = 0; + m_attrib.Set().minValue = min; + m_cacheUpdate = true; + if (draw) + UpdateDisplay(); + } + + int UISlider::GetMinValue() const + { + return m_attrib.Get().minValue; + } + + void UISlider::OnPaintProc(MPCPaintParam param) + { + CalcDrawRect(); + UIRect dstDraw = m_drawRect; + UIRect dstBg = m_drawBgRect; + UIRect dstBtn = m_drawBtnRect; + if (param->cacheCanvas) + { + int x = -(int)UINodeBase::m_data.Frame.left; + x += param->destRect->left; + int y = -(int)UINodeBase::m_data.Frame.top; + y += param->destRect->top; + dstDraw.Offset(x, y); + dstBg.Offset(x, y); + dstBtn.Offset(x, y); + } + _m_byte alpha = param->blendedAlpha; + + auto scale = GetRectScale().scale(); + if (const auto style = m_attrib.Get().style) + { + style->PaintStyle(param->render, &dstBg, alpha, IsEnabled() ? UISliderTrackNormal : UISliderTrackDisable, 4, scale); + style->PaintStyle(param->render, &dstDraw, alpha, IsEnabled() ? UISliderBlockNormal : UISliderBlockDisable, 4, scale); + } + if(const auto style = m_attrib.Get().btnStyle) + style->PaintStyle(param->render, &dstBtn, alpha, IsEnabled() ? btnStatus : UISliderBtnDisable, 4, scale); + + if (auto&& dbgframe = GetDbgFramePen()) + param->render->DrawRectangle(dstBtn, dbgframe); + } + + bool UISlider::OnMouseMessage(MEventCodeEnum message, _m_param wParam, _m_param lParam) + { + using namespace Helper; + const UIPoint pt = M_GetMouseEventPt(lParam); + switch (message) + { + case M_MOUSE_MOVE: + { + if (Rect::IsPtInside(m_drawBtnRect, pt) && + btnStatus != UISliderBtnHover && btnStatus != UISliderBtnPressed) + { + btnStatus = UISliderBtnHover; + m_cacheUpdate = true; + UpdateDisplay(); + } + else if (!m_isClick && !Rect::IsPtInside(m_drawBtnRect, pt) && + btnStatus == UISliderBtnHover) + { + btnStatus = UISliderBtnNormal; + m_cacheUpdate = true; + UpdateDisplay(); + } + if (m_isClick) + { + SetCurValue(CalcPtValue(pt)); + if (int value = m_attrib.Get().value; m_oldValue != value) + { + m_oldValue = value; + SendEvent(Event_Slider_Change, value); + mslot.valueChanged.Emit(value); + } + } + break; + } + case M_MOUSE_LBDOWN: + { + m_isClick = true; + m_hitbtn = Rect::IsPtInside(m_drawBtnRect, pt); + m_downpos = pt; + m_downpos.x = pt.x - m_drawBtnRect.left; + m_downpos.y = pt.y - m_drawBtnRect.top; + + if(!m_hitbtn) + SetCurValue(CalcPtValue(pt)); + if (int value = m_attrib.Get().value; m_oldValue != value) + { + m_oldValue = value; + SendEvent(Event_Slider_Change, value); + mslot.valueChanged.Emit(value); + } + SetCapture(); + break; + } + case M_MOUSE_LBUP: + { + if (m_isClick) + { + ReleaseCapture(); + m_isClick = false; + m_hitbtn = false; + SendEvent(Event_Mouse_LClick); + } + break; + } + case M_MOUSE_LEAVE: + { + btnStatus = UISliderBtnNormal; + m_cacheUpdate = true; + UpdateDisplay(); + break; + } + default: + break; + } + return UIControl::OnMouseMessage(message, wParam, lParam); + } + + int UISlider::Percentage(int value1, int percentValue, int percent) + { + //化小数运算 避免乘法溢出 + const float v1 = (float)value1 / 100.f; + const float v2 = (float)percentValue / 100.f; + const float v3 = (float)percent / 100.f; + const float ret = v1 * v2 / v3; + return (int)(ret * 100.f); + } + + void UISlider::CalcDrawRect() + { + m_drawRect = Frame(); + + auto& attrib = m_attrib.Get(); + + _m_scale scale = GetRectScale().scale(); + + //track内边距 + auto trackInset = attrib.trackInset; + m_drawRect.left += _scale_to(trackInset.left, scale.cx); + m_drawRect.top += _scale_to(trackInset.top, scale.cy); + m_drawRect.right -= _scale_to(trackInset.right, scale.cx); + m_drawRect.bottom -= _scale_to(trackInset.bottom, scale.cy); + + m_drawBgRect = m_drawRect; + + int frameHeight = (int)UINodeBase::m_data.Frame.GetHeight(); + int frameWidth = (int)UINodeBase::m_data.Frame.GetWidth(); + + //按钮矩形计算 如果=0自动计算 + UISize btnSize = attrib.btnSize; + btnSize.width = _scale_to(btnSize.width, scale.cx); + btnSize.height = _scale_to(btnSize.height, scale.cy); + + if (attrib.vertical) + { + //防止按钮大小超出frame + if (btnSize.width > frameWidth || btnSize.width == 0) + btnSize.width = frameWidth; + if (btnSize.height == 0) + btnSize.height = frameWidth; + + //track值和按钮矩形计算 + int btnOffset = btnSize.height / 2; + int trackHeight = m_drawRect.GetHeight(); + int calcValue = Percentage(attrib.value, trackHeight - btnSize.height, attrib.maxValue); + + if (attrib.bottomShow) + { + m_drawRect.top += trackHeight - calcValue - btnOffset; + m_drawBtnRect.top = m_drawRect.top - btnOffset; + } + else + { + m_drawRect.bottom -= trackHeight - calcValue - btnOffset; + + m_drawBtnRect.top = m_drawRect.bottom - btnOffset; + } + m_drawBtnRect.bottom = m_drawBtnRect.top + btnSize.height; + //如果按钮小于frame 居中显示 + m_drawBtnRect.left = (int)UINodeBase::m_data.Frame.left + ((frameWidth - btnSize.width) / 2); + m_drawBtnRect.right = m_drawBtnRect.left + btnSize.width; + } + else + { + if (btnSize.height > frameHeight || btnSize.height == 0) + btnSize.height = frameHeight; + if (btnSize.width == 0) + btnSize.width = frameHeight; + + int btnOffset = btnSize.width / 2; + int trackWidth = m_drawRect.GetWidth(); + int calcValue = Percentage(attrib.value, trackWidth - btnSize.width, attrib.maxValue); + if (!attrib.leftShow) + { + m_drawRect.left += trackWidth - calcValue - btnOffset; + m_drawBtnRect.left = m_drawRect.left - btnOffset; + } + else + { + m_drawRect.right -= trackWidth - calcValue - btnOffset; + m_drawBtnRect.left = m_drawRect.right - btnOffset; + } + m_drawBtnRect.right = m_drawBtnRect.left + btnSize.width; + m_drawBtnRect.top = (int)UINodeBase::m_data.Frame.top + ((frameHeight - btnSize.height) / 2); + m_drawBtnRect.bottom = m_drawBtnRect.top + btnSize.height; + } + } + + int UISlider::CalcPtValue(UIPoint pt) + { + //鼠标在控件内的位置 + const UIPoint curPt = + { + pt.x - m_drawBgRect.left, + pt.y - m_drawBgRect.top + }; + + auto& attrib = m_attrib.Get(); + int ret; + if (attrib.vertical) + { + int height[2] = { m_drawBtnRect.GetHeight(), m_drawBgRect.GetHeight() }; + int offset = height[0] / 2; + if (m_hitbtn) + offset = m_downpos.y; + int calcValue = Helper::M_MIN(height[1], curPt.y - offset); + ret = Percentage(calcValue, attrib.maxValue, height[1] - height[0]); + //反向 + if (attrib.bottomShow) + ret = (int)attrib.maxValue - ret; + } + else + { + int width[2] = { m_drawBtnRect.GetWidth(), m_drawBgRect.GetWidth() }; + int offset = width[0] / 2; + if (m_hitbtn) + offset = m_downpos.x; + int calcValue = Helper::M_MIN(width[1], curPt.x - offset + 1); + ret = Percentage(calcValue, attrib.maxValue, width[1] - width[0]); + + if (!attrib.leftShow) + ret = (int)attrib.maxValue - ret; + + m_cacheUpdate = true; + UpdateDisplay(); + } + if (ret < 0) + ret = 0; + if (ret < attrib.minValue) + ret = attrib.minValue; + + return ret; + } + + void UISlider::BindAttribute() + { + auto list = + { + MakeUIAttrib(m_attrib, &Attribute::style, L"style"), + MakeUIAttrib(m_attrib, &Attribute::btnStyle, L"btnStyle"), + MakeUIAttrib(m_attrib, &Attribute::maxValue, L"maxValue"), + MakeUIAttrib(m_attrib, &Attribute::minValue, L"minValue"), + MakeUIAttrib(m_attrib, &Attribute::value, L"value"), + MakeUIAttrib(m_attrib, &Attribute::vertical, L"vertical"), + MakeUIAttrib(m_attrib, &Attribute::leftShow, L"leftShow"), + MakeUIAttrib(m_attrib, &Attribute::bottomShow, L"bottomShow"), + MakeUIAttrib(m_attrib, &Attribute::trackInset, L"trackInset"), + MakeUIAttrib(m_attrib, &Attribute::btnSize, L"btnSize") + }; + + decltype(m_attrib)::RegisterAttrib(list); + } +} diff --git a/MiaoUI/src/source/FileSystem/DreamMoonRes.cpp b/MiaoUI/src/source/FileSystem/DreamMoonRes.cpp new file mode 100644 index 0000000..8a30e80 --- /dev/null +++ b/MiaoUI/src/source/FileSystem/DreamMoonRes.cpp @@ -0,0 +1,1002 @@ +/** + * FileName: DreamMoonRes.cpp + * Note: 梦月资源类实现 + * + * Copyright (C) 2020-2022 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-12-8 Create +*/ +#include +#include + +#if MUI_CFG_ENABLE_V1DMRES + +#define wsize 2 +#define clsnameMax 30 + +#if MUI_CFG_ENABLE_CRYPTOPP +#define DMRES_USE_CRYPTOPP +#endif + +#ifdef DMRES_USE_CRYPTOPP +#include +#include +#include +#include +#include +#include +#else +#include +#include +#endif // DMRES_USE_CRYPTOPP + +#ifdef __ANDROID__ +#include +#include +#include "errno.h" +#endif + +namespace Mui { + + DMResources::~DMResources() + { + CloseResource(); + } + + bool DMResources::LoadResource(std::wstring resfiles, bool mem) + { + if (m_files || m_resource.data) + return false; + if (mem) { + m_resource = ReadFiles(resfiles); + if (!m_resource.data) + return false; + bool ret = LoadResList(!mem); + if (!ret) + CloseResource(); + return ret; + } + else { +#ifdef _WIN32 + m_files = _wfsopen(resfiles.c_str(), L"rb", _SH_DENYNO); + //_wfopen_s(&m_files, resfiles.c_str(), L"rb"); +#endif // _WIN32 +#ifdef __ANDROID__ + m_files = fopen((char*)M_WStringToString(resfiles).c_str(), "rb"); +#endif // __ANDROID__ + if (m_files) { + bool ret = LoadResList(!mem); + if (!ret) + CloseResource(); + return ret; + } + } + return false; + } + +#ifdef _WIN32 + + UIResource DMResources::ReadPEResource(DWORD resID, LPCWSTR type) + { + UIResource ret; + HINSTANCE hDll = GetModuleHandle(NULL); + HRSRC hRes = FindResourceW(hDll, MAKEINTRESOURCE(resID), type); + HGLOBAL hgRes = ::LoadResource(hDll, hRes); + DWORD nResSize = SizeofResource(hDll, hRes); + BYTE* pRes = (BYTE*)LockResource(hgRes); + ret.data = new _m_byte[nResSize]; + ret.size = nResSize; + memcpy(ret.data, pRes, nResSize); + UnlockResource(hgRes); + FreeResource(hgRes); + return ret; + } + + bool DMResources::LoadResource(_m_ulong resid, _m_lpcwstr restype) + { + if (m_resource.data || m_resource.size) + return false; + m_resource = ReadPEResource(resid, restype); + if (m_resource.data && m_resource.size) + return LoadResList(); + return false; + } + +#endif // _WIN32 + + bool DMResources::LoadResource(UIResource memres) + { + if (m_resource.data || m_resource.size || !memres.data || memres.size == 0) + return false; + + m_resource.size = memres.size; + m_resource.data = (_m_byte*)malloc(memres.size); + + memcpy(m_resource.data, memres.data, memres.size); + return LoadResList(); + } + + _m_size DMResources::GetResourceCount() + { + return (_m_size)dmResList.size(); + } + + DMResKey DMResources::ReadResource(std::wstring resname, std::wstring reskey, ResType type) + { + DMResKey key; + mx.lock(); + for (auto item : dmResList) + { + if (item.resname == resname) + { + if (m_files) + { + if (type == AllRes || type == DataRes) { + if (item.resPos.second != 0) + { + item.res.data = new _m_byte[item.resPos.second]; + fseek(m_files, item.resPos.first, SEEK_SET); + fread(item.res.data, 1, item.resPos.second, m_files); + int err = ferror(m_files); + key.res = Deciphering(item.res, reskey); + delete[] item.res.data; + } + } + if (type == AllRes || type == StringRes) { + if (item.strPos.second != 0) + { + char16_t* str = new char16_t[item.strPos.second]; + fseek(m_files, item.strPos.first, SEEK_SET); + fread(str, 1, item.strPos.second * wsize, m_files); + key.resText = DecipheringText({ (_m_byte*)str, item.strPos.second }, reskey); + delete[] str; + } + } + } + else + { + if (type == AllRes || type == DataRes) { + if (item.res.data) + key.res = Deciphering(item.res, reskey); + } + if (type == AllRes || type == StringRes) { + if (item.text.data) + key.resText = DecipheringText(item.text, reskey); + } + } + break; + } + } + mx.unlock(); + return key; + } + + UIResource DMResources::ReadResourceBlock(std::wstring resname, std::wstring reskey, _m_uint offset, _m_size len, _m_size* retlen) + { + UIResource res; + mx.lock(); + for (auto item : dmResList) + { + if (item.resname == resname) + { + if (m_files) + { + if (item.resPos.second != 0) + { + _m_size readsize = len; + //偏移量超过数据大小 返回空 + if (offset > item.resPos.second) { + mx.unlock(); + return res; + } + //偏移量+要读取的数据大于剩余数据 + else if (offset + len > item.resPos.second) + readsize = item.resPos.second - offset;//取可读的数据 + + item.res.data = new _m_byte[readsize]; + fseek(m_files, item.resPos.first + offset, SEEK_SET); + _m_size read = (_m_size)fread(item.res.data, 1, readsize, m_files); + int err = ferror(m_files); + item.res.size = read; + res = Deciphering(item.res, reskey); + delete[] item.res.data; + if (retlen) + *retlen = read; + } + } + else + { + if (item.res.data) { + _m_size readsize = len; + if (offset > item.res.size) { + mx.unlock(); + return res; + } + else if (offset + len > item.res.size) + readsize = item.res.size - offset; + item.res.data += offset; + item.res.size = readsize; + res = Deciphering(item.res, reskey); + if (retlen) + *retlen = readsize; + } + } + break; + } + } + mx.unlock(); + return res; + } + + _m_size DMResources::ReadResourceSize(std::wstring resname, ResType type) + { + std::lock_guard lock(mx); + for (auto item : dmResList) + { + if (item.resname == resname) + { + if (m_files) + { + if (type == DataRes) + return item.resPos.second; + else if (type == StringRes && item.strPos.second) + return (_m_size)((double)item.strPos.second / (double)sizeof(char16_t)); + } + else + { + if (type == DataRes) + return item.res.size; + else if (type == StringRes && item.text.size) + return (_m_size)((double)item.text.size / (double)sizeof(char16_t)); + } + } + } + return 0; + } + + bool DMResources::ReadResBlockInfo(std::wstring resname, _m_size* blocksize) + { + for (auto item : dmResList) + { + if (item.resname == resname) + { + if (blocksize) + *blocksize = item.blockSize; + return item.block; + } + } + return false; + } + + bool DMResources::EnumResourceName(std::vector& nameList) + { + nameList.clear(); + if (!m_resource.data) + return false; + mx.lock(); + for (auto item : dmResList) + { + nameList.push_back(item.resname); + } + mx.unlock(); + return true; + } + + _m_lpcwstr DMResources::GetResClassName() + { + return m_resheader.classname.c_str(); + } + + + bool DMResources::CreateResource(std::wstring classname) + { + if (classname.length() > clsnameMax) + return false; + if (!m_resource.data && !m_resource.size) + { + m_resheader.classname = classname; + m_resource.data = (_m_byte*)1; + return true; + } + return false; + } + + bool DMResources::AddResource(DMResKey res, std::wstring resname, std::wstring reskey, DMEncBlockCallback callback) + { + if (!m_resource.data) + return false; + DMResItem item; + item.resname = resname; + + if (res.res.data && res.res.size) + { + if (res.block) + { + if (callback.callback) + { + if (!res.resText.empty()) + item.text = EncipheringText(res.resText, reskey); + mx.lock(); + dmResList.push_back(item); + mx.unlock(); + EncBlockResource(res.res, resname, reskey, (_m_uint)res.blockSize, callback); + return true; + } else + item.res = EncBlockResource(res.res, resname, reskey, (_m_uint)res.blockSize, callback); + } + else + item.res = Enciphering(res.res, reskey); + } + if (!res.resText.empty()) + item.text = EncipheringText(res.resText, reskey); + + dmResList.push_back(item); + return true; + } + + bool DMResources::RenameResource(std::wstring resname, std::wstring newname) + { + if (!m_resource.data) + return false; + std::lock_guard lock(mx); + for (size_t i = 0; i < dmResList.size(); i++) + { + if (dmResList[i].resname == resname) + { + dmResList[i].resname = newname; + return true; + } + } + return false; + } + + void DMResources::RenameClassName(std::wstring newname) + { + m_resheader.classname = newname; + } + + bool DMResources::ChangeResource(std::wstring resname, DMResKey newres, std::wstring reskey, ResType type, + DMEncBlockCallback callback) + { + if (!m_resource.data) + return false; + for (size_t i = 0; i < dmResList.size(); i++) + { + if (dmResList[i].resname == resname) + { + //释放原始资源 + if (type == AllRes || type == DataRes) { + if (dmResList[i].res.data && dmResList[i].res.size) + dmResList[i].res.Release(); + + if (newres.res.data && newres.res.size) { + dmResList[i].block = newres.block; + if (newres.block) { + dmResList[i].blockSize = newres.blockSize; + dmResList[i].res = EncBlockResource(newres.res, resname, reskey, newres.blockSize, callback); + } + else { + dmResList[i].blockSize = 0; + dmResList[i].res = Enciphering(newres.res, reskey); + } + } + } + if (type == AllRes || type == StringRes) { + if (dmResList[i].text.data && dmResList[i].text.size) + dmResList[i].text.Release(); + + if (newres.resText != L"") + dmResList[i].text = EncipheringText(newres.resText, reskey); + } + return true; + } + } + return false; + } + + bool DMResources::DeleteResource(std::wstring resname) + { + if (!m_resource.data) + return false; + std::lock_guard lock(mx); + for (size_t i = 0; i < dmResList.size(); i++) + { + if (dmResList[i].resname == resname) + { + //释放资源 + if (dmResList[i].res.data && dmResList[i].res.size) + dmResList[i].res.Release(); + if (dmResList[i].text.data && dmResList[i].text.size) + dmResList[i].text.Release(); + + dmResList.erase(dmResList.begin() + i); + return true; + } + } + return false; + } + + bool DMResources::SaveResource(std::wstring filename) + { + m_resheader.rescount = (_m_uint)dmResList.size(); + m_resheader.ressign = L"DreamMoonResFile"; + m_resheader.resver = DMResVer; + + FILE* file; +#ifdef _WIN32 + _wfopen_s(&file, filename.c_str(), L"wb"); +#else +#ifdef __ANDROID__ + char* path = (char *)M_WStringToString(filename).c_str(); + + file = fopen(path, "wb+"); + //__android_log_print(ANDROID_LOG_ERROR, "ERRON:", "%d", errno); +#endif +#endif + if (!file) + return false; + + /*写文件头*/ + + //写入类名长度和类名 + UIResource classname = EncipheringText(m_resheader.classname, L"DMResFile"); + _m_uint strLen = (_m_uint)classname.size; + fwrite(&strLen, 1, sizeof(strLen), file); + fwrite(classname.data, 1, strLen, file); + classname.Release(); + //写入签名长度和签名 + strLen = (_m_uint)m_resheader.ressign.length(); + fwrite(&strLen, 1, sizeof(strLen), file); +#ifdef _WIN32 + fwrite(m_resheader.ressign.data(), 1, m_resheader.ressign.length() * wsize, file); +#endif +#ifdef __ANDROID__ + char16_t * str = wchar32Towchar16((wchar_t *)m_resheader.ressign.data(), m_resheader.ressign.length()); + fwrite(str, 1, m_resheader.ressign.length() * wsize, file); + delete[] str; +#endif + //写入版本号长度和版本号 + strLen = (_m_uint)m_resheader.resver.length(); + fwrite(&strLen, 1, sizeof(strLen), file); +#ifdef _WIN32 + fwrite(m_resheader.resver.data(), 1, m_resheader.resver.length() * wsize, file); +#endif +#ifdef __ANDROID__ + str = wchar32Towchar16((wchar_t *)m_resheader.resver.data(), m_resheader.resver.length()); + fwrite(str, 1, m_resheader.resver.length() * wsize, file); + delete[] str; +#endif + //写入资源数量 + fwrite(&m_resheader.rescount, 1, sizeof(m_resheader.rescount), file); + + /*写资源列表数据*/ + for (auto item : dmResList) + { + //写资源长度和资源 + _m_uint size = (_m_uint)item.res.size; + fwrite(&size, 1, sizeof(size), file); + if (size != 0) + fwrite(item.res.data, 1, item.res.size, file); + //写文本数据长度和数据 + size = (_m_uint)item.text.size; + fwrite(&size, 1, sizeof(size), file); + if (size != 0) + fwrite(item.text.data, 1, item.text.size, file); + //写资源名文本长度 + size = (_m_uint)item.resname.length(); + fwrite(&size, 1, sizeof(size), file); + if (size != 0) { +#ifdef _WIN32 + fwrite(item.resname.data(), 1, size * wsize, file); +#endif +#ifdef __ANDROID__ + str = wchar32Towchar16((wchar_t *)item.resname.data(), strLen); + fwrite(str, 1, strLen * wsize, file); + delete[] str; +#endif + } + //写块资源长度和数据 + size = item.blockSize; + fwrite(&size, 1, sizeof(size), file); + } + + if (fclose(file) == 0) + return true; + + return false; + } + + bool DMResources::LoadResList(bool files) + { + //当前读取内存位置 + _m_ulong curMemPos = 0; + //读取类名二进制字符串长度 + _m_uint strLen = 0; + if (files) { + fseek(m_files, 0, SEEK_SET); + fread(&strLen, 1, sizeof(_m_uint), m_files); + } else + memcpy(&strLen, m_resource.data, sizeof(_m_uint)); + if (strLen > clsnameMax) + return false; + //读取类名 + char16_t* str = (char16_t*)new char16_t[strLen + 1]; + curMemPos = sizeof(_m_uint);//一个UINT数据记录二进制字节长度 + if (files) { + fseek(m_files, curMemPos, SEEK_SET); + fread(str, 1, strLen * wsize, m_files); + } else + memcpy(str, m_resource.data + curMemPos, strLen * wsize); + str[strLen] = '\0';//添加字符串结尾 + m_resheader.classname = DecipheringText({ (_m_byte*)str, strLen }, L"DMResFile"); + delete[] str; + //读取签名长度 + curMemPos += strLen;//原位置加上原数据长度 后面是存签名长度的UINT类型 + strLen = 0; + if (files) { + fseek(m_files, curMemPos, SEEK_SET); + fread(&strLen, 1, sizeof(_m_uint), m_files); + } + else + memcpy(&strLen, m_resource.data + curMemPos, sizeof(_m_uint)); + //读取签名 + str = new char16_t[strLen + 1]; + curMemPos += sizeof(_m_uint);//过UINT数据后是签名字符串数据 + if (files) { + fseek(m_files, curMemPos, SEEK_SET); + fread(str, 1, strLen * wsize, m_files); + } + else + memcpy(str, m_resource.data + curMemPos, strLen * wsize); + str[strLen] = '\0';//添加字符串结尾 + m_resheader.ressign = wchar16Towchar32(str, strLen); + delete[] str; + //读取版本号长度 + curMemPos += strLen * wsize;//上一个位置加上字符串数据长度 后面是存版本号长度的UINT类型 + strLen = 0; + if (files) { + fseek(m_files, curMemPos, SEEK_SET); + fread(&strLen, 1, sizeof(_m_uint), m_files); + } + else + memcpy(&strLen, m_resource.data + curMemPos, sizeof(_m_uint)); + //读取版本号 + str = new char16_t[strLen * wsize]; + curMemPos += sizeof(_m_uint);//过UINT数据后是签名字符串数据 + if (files) { + fseek(m_files, curMemPos, SEEK_SET); + fread(str, 1, strLen * wsize, m_files); + } + else + memcpy(str, m_resource.data + curMemPos, strLen * wsize); + str[strLen] = '\0';//添加字符串结尾 + m_resheader.resver = wchar16Towchar32(str, strLen); + delete[] str; + //读取资源数量 + curMemPos += strLen * wsize; + strLen = 0; + if (files) { + fseek(m_files, curMemPos, SEEK_SET); + fread(&strLen, 1, sizeof(_m_uint), m_files); + } + else + memcpy(&strLen, m_resource.data + curMemPos, sizeof(_m_uint)); + m_resheader.rescount = strLen; + + if (m_resheader.classname == L"" + || !m_resheader.rescount || m_resheader.ressign == L"" + || m_resheader.resver == L"") + return false; + else { + /*读取资源列表数据*/ + _m_uint dataLen = 0; + curMemPos += sizeof(_m_uint);//过资源数量后 地址是资源列表的开始 + for (size_t i = 0; i < m_resheader.rescount; i++) + { + /*数据资源*/ + UIResource data; + DMResItem item; + //读取资源数据长度 + dataLen = 0; + if (files) + { + fseek(m_files, curMemPos, SEEK_SET); + fread(&dataLen, 1, sizeof(_m_uint), m_files); + } else + memcpy(&dataLen, m_resource.data + curMemPos, sizeof(_m_uint)); + curMemPos += sizeof(_m_uint);//过数据长度 地址是数据开始位置 + data.size = dataLen; + if (dataLen != 0 && !files) + { + _m_byte* res = new _m_byte[dataLen]; + //读取数据 + memcpy(res, m_resource.data + curMemPos, data.size); + data.data = res; + } + item.res = data; + item.resPos = std::make_pair(curMemPos, (_m_uint)data.size); + + /*文本资源*/ + curMemPos += (_m_ulong)data.size;//文本数据长度地址 + dataLen = 0; + if (files) + { + fseek(m_files, curMemPos, SEEK_SET); + fread(&dataLen, 1, sizeof(_m_uint), m_files); + } + else + memcpy(&dataLen, m_resource.data + curMemPos, sizeof(_m_uint)); + curMemPos += sizeof(_m_uint); + UIResource dataText; + dataText.size = dataLen; + if (dataLen != 0 && !files) + { + _m_byte* res = new _m_byte[dataLen]; + //读取数据 + memcpy(res, m_resource.data + curMemPos, dataText.size); + dataText.data = res; + } + item.text = dataText; + item.strPos = std::make_pair(curMemPos, (_m_uint)dataText.size); + + /*资源名*/ + curMemPos += (_m_ulong)dataText.size; + + dataLen = 0; + if (files) { + fseek(m_files, curMemPos, SEEK_SET); + fread(&dataLen, 1, sizeof(_m_uint), m_files); + } else + memcpy(&dataLen, m_resource.data + curMemPos, sizeof(_m_uint)); + curMemPos += sizeof(_m_uint); + if (dataLen != 0) + { + char16_t* str = new char16_t[dataLen + 1]; + if (files) { + fseek(m_files, curMemPos, SEEK_SET); + fread(str, 1, dataLen * wsize, m_files); + } else + memcpy(str, m_resource.data + curMemPos, dataLen * wsize); + str[dataLen] = '\0';//添加字符串结尾 + item.resname = wchar16Towchar32(str, dataLen); + delete[] str; + } + curMemPos += dataLen * wsize;//过文本数据长度后是块尺寸 + + /*块信息*/ + dataLen = 0; + if (files) { + fseek(m_files, curMemPos, SEEK_SET); + fread(&dataLen, 1, sizeof(_m_uint), m_files); + } + else + memcpy(&dataLen, m_resource.data + curMemPos, sizeof(_m_uint)); + + if (dataLen) { + item.block = true; + item.blockSize = dataLen; + } + + dmResList.push_back(item); + curMemPos += sizeof(_m_uint); + } + if(!files) + free(m_resource.data); + m_resource.data = (_m_byte*)1; + return true; + } + + return false; + } + + bool DMResources::CloseResource() + { + if (m_files) { + fclose(m_files); + m_files = nullptr; + } + for (size_t i = 0; i < dmResList.size(); i++) + { + if (dmResList[i].res.data) + dmResList[i].res.Release(); + if (dmResList[i].text.data) + dmResList[i].text.Release(); + } + dmResList.clear(); + m_resheader.classname = L""; + m_resheader.rescount = 0; + m_resheader.ressign = L""; + m_resheader.resver = L""; + m_resource = { 0, 0 }; + return true; + } + + UIResource DMResources::ReadFiles(std::wstring filepath, bool string) + { + FILE* file; +#ifdef _WIN32 + _wfopen_s(&file, filepath.c_str(), L"rb"); +#else +#ifdef __ANDROID__ + file = fopen((char*)M_WStringToString(filepath).c_str(), "rb"); +#endif +#endif + if (!file) + return UIResource(); + fseek(file, 0L, SEEK_END); + _m_ulong len = ftell(file); + rewind(file); + //读文本长度+1 + _m_byte* data = (_m_byte*)malloc(string ? len + 1 : len); + //如果读取文本 结尾添加\0 + if (string) + data[len] = '\0'; + len = (_m_ulong)fread(data, 1, len, file); + fclose(file); + UIResource buffer = { data, len }; + + return buffer; + } + + bool DMResources::WriteFiles(std::wstring filepath, UIResource res) + { + _m_ulong fSize = (_m_ulong)res.size; + FILE* file; +#ifdef _WIN32 + _wfopen_s(&file, filepath.c_str(), L"wb"); +#else +#ifdef __ANDROID__ + file = fopen((char*)M_WStringToString(filepath).c_str(), "wb"); +#endif +#endif + if (!file) + return false; + _m_ulong len = (_m_ulong)fwrite(res.data, 1, fSize, file); + fclose(file); + return len == fSize; + } + + _m_byte* DMResources::HexStringToBytes(std::string str) + { + auto charToByte = [](char c) -> _m_byte { //bug abcdef会被转化成0 因为输入的sha256是大写 将错就错了 不改了 + return (_m_byte)static_cast("0123456789abcdef").find(c); + }; + + int length = (int)str.length() / 2; + _m_byte* b = new _m_byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + b[i] = (_m_byte)(charToByte(str[pos]) << 4 | charToByte(str[pos + 1])); + } + return b; + } + + std::string DMResources::Sha256StringHex(std::wstring str) + { + auto achar = Helper::M_WStringToString(str); +#ifdef DMRES_USE_CRYPTOPP + CryptoPP::SHA256 hash; + byte digest[CryptoPP::SHA256::DIGESTSIZE]; + + hash.CalculateDigest(digest, (byte*)achar.c_str(), (int)str.length()); + + CryptoPP::HexEncoder encoder; + std::string output; + encoder.Attach(new CryptoPP::StringSink(output)); + encoder.Put(digest, sizeof(digest)); + encoder.MessageEnd(); + return output; +#else + std::string hash_hex_str; + picosha2::hash256_hex_string(achar, hash_hex_str); + std::transform(hash_hex_str.begin(), hash_hex_str.end(), hash_hex_str.begin(), ::toupper); + return hash_hex_str; +#endif + } + + std::wstring DMResources::wchar16Towchar32(char16_t* wchar16, _m_uint len) { +#ifdef _WIN32 + return std::wstring((wchar_t*)wchar16); +#endif +#ifdef __ANDROID__ + wchar_t* wcs = new wchar_t [len + 1]; + int u32; + unsigned short* end = (u_short*)(wchar16 + len); + wchar_t* ret = wcs; + int u32low = 0; + while ((u_short*)(wchar16) < end) + { + u32 = *wchar16++; + if (u32 >= 0xD800 && u32 < 0xDC00) + { + u32low = *wchar16++; + u32 &= 0x3FF; + u32low &= 0x3FF; + u32 <<= 10; + u32 += u32low; + u32 += 0x10000; + } + *wcs++ = u32; + } + ret[len] = L'\0'; + std::wstring ret1 = ret; + delete[] ret; + return ret1; +#endif + } + + char16_t * DMResources::wchar32Towchar16(wchar_t * wchar32, _m_uint len) + { + int wc; + char16_t * utf16 = new char16_t [len]; + wchar_t *end = wchar32 + len; + unsigned short * ret = (unsigned short *)utf16; + while(wchar32 < end) + { + wc = *(wchar32++); + if (wc > 0xFFFF) + { + wc -= 0x00010000L; + *utf16++ = 0xD800 | (wc >> 10); + *utf16++ = 0xDC00 | (wc & 0x03FF); + } + else + { + *utf16++ = (char16_t)wc; + } + } + //ret[len] = '\0'; + return reinterpret_cast(ret); + } + + UIResource DMResources::Enciphering(UIResource res, std::wstring key) + { + _m_byte * key_ = HexStringToBytes(Sha256StringHex(key)); + _m_byte* iv_ = HexStringToBytes(Sha256StringHex(key).substr(0, 32)); + + _m_byte* pOut = new _m_byte[res.size]; + +#ifdef DMRES_USE_CRYPTOPP + CryptoPP::CFB_Mode::Encryption encryption(key_, CryptoPP::AES::MAX_KEYLENGTH, iv_); + encryption.ProcessData(pOut, res.data, res.size); + res.data = pOut; +#else + AES256_CFB(res, pOut, key_, iv_, true); + res.data = pOut; +#endif + delete[] key_; + delete[] iv_; + return res; + } + + UIResource DMResources::EncipheringText(std::wstring text, std::wstring key) + { + UIResource res; +#ifdef _WIN32 + res.data = (_m_byte*)text.data(); +#endif +#ifdef __ANDROID__ + char16_t * str = wchar32Towchar16((wchar_t *)text.data(), text.length()); + res.data = (_m_byte*)str; +#endif + res.size = (_m_size)text.size() * wsize; + res = Enciphering(res, key); +#ifdef __ANDROID__ + delete[] str; +#endif + return res; + } + + UIResource DMResources::Deciphering(UIResource res, std::wstring key) + { + _m_byte* key_ = HexStringToBytes(Sha256StringHex(key)); + _m_byte* iv_ = HexStringToBytes(Sha256StringHex(key).substr(0, 32)); + + _m_byte* pOut = new _m_byte[res.size]; + +#ifdef DMRES_USE_CRYPTOPP + CryptoPP::CFB_Mode::Decryption decryption(key_, CryptoPP::AES::MAX_KEYLENGTH, iv_); + decryption.ProcessData(pOut, res.data, res.size); + res.data = pOut; +#else + AES256_CFB(res, pOut, key_, iv_, false); + res.data = pOut; +#endif + delete[] key_; + delete[] iv_; + return res; + } + + std::wstring DMResources::DecipheringText(UIResource res, std::wstring key) + { + res.size += wsize; + UIResource out = Deciphering(res, key); + for (size_t i = 0; i < wsize; i++) + { + out.data[out.size - 1 - i] = '\0'; + } + + std::wstring ret = wchar16Towchar32((char16_t*)out.data, _m_uint(res.size / 2)); + ret = ret; + out.Release(); + return ret; + } + + void DMResources::EncBlockProc(UIResource res, std::wstring resname, std::wstring key, _m_uint blocksize, UIResource* ret, DMEncBlockCallback callback) + { + _m_byte* buffer = new _m_byte[res.size]; + _m_size ensize = 0;//已编码资源 + _m_ulong offset = 0;//偏移指针 + while (ensize != res.size) + { + //要写入内存的大小 + _m_size writeSize = blocksize; + //如果剩余数据小于块大小 将块大小设置为剩余数据大小 + if (offset + blocksize > res.size) + writeSize = res.size - offset; + + UIResource block; + block.data = res.data + offset; + block.size = writeSize; + + UIResource encData = Enciphering(block, key); + memcpy(buffer + offset, encData.data, writeSize); + encData.Release(); + + offset += blocksize; + ensize += writeSize; + if (callback.callback) + callback.callback(ensize, res.size, callback.param); + } + if (callback.callback) + { + for (size_t i = 0; i < dmResList.size(); i++) + { + DMResItem* item = &dmResList[i]; + if (item->resname == resname) + { + if (item->res.data) + item->res.Release(); + item->res.data = buffer; + item->res.size = res.size; + return; + } + } + delete[] buffer; + } + else if (ret) + *ret = UIResource(buffer, res.size); + else + delete[] buffer; + } + + UIResource DMResources::EncBlockResource(UIResource res, std::wstring resname, std::wstring key, _m_uint blocksize, DMEncBlockCallback callback) + { + if (blocksize > res.size || blocksize == 0) + return Enciphering(res, key); + else + { + std::thread encthread = std::thread(&DMResources::EncBlockProc, this, res, resname, key, blocksize, &res, callback); + if(callback.callback) + encthread.detach(); + else { + encthread.join(); + return res; + } + } + return UIResource(); + } +} +#endif \ No newline at end of file diff --git a/MiaoUI/src/source/FileSystem/Mui_FileSystem.cpp b/MiaoUI/src/source/FileSystem/Mui_FileSystem.cpp new file mode 100644 index 0000000..c366144 --- /dev/null +++ b/MiaoUI/src/source/FileSystem/Mui_FileSystem.cpp @@ -0,0 +1,335 @@ +/** + * FileName: Mui_FileSystem.cpp + * Note: 基本文件系统操作 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-10-9 Create +*/ +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +#pragma warning(disable:4003) + +#define CATCH_FSERROR(x) catch (const std::filesystem::filesystem_error& ex) \ +{ \ + if (ex.code() == std::errc::permission_denied) \ + MErrorThrow(MErrorCode::AccessDenied); \ + else if (ex.code() == std::errc::io_error) \ + MErrorThrow(MErrorCode::IOError); \ + else \ + MErrorThrow(ex.what()); \ + return x; \ +} + +namespace Mui::FS +{ + bool UI::MBrowseForFile(bool isOpen, bool multiple, const std::vector& filter, _m_param parentWndHandle, + std::vector& selectedFiles, std::wstring_view defExtName) + { +#ifdef _WIN32 + IFileDialog* fileDialog = nullptr; + DWORD flags = FOS_PATHMUSTEXIST | FOS_FILEMUSTEXIST; + if (multiple) + flags |= FOS_ALLOWMULTISELECT; + + HRESULT hr = CoCreateInstance(isOpen ? CLSID_FileOpenDialog : CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC_SERVER, + isOpen ? IID_IFileOpenDialog : IID_IFileSaveDialog, (LPVOID*)&fileDialog); + if (FAILED(hr)) + return false; + + //设置文件过滤器 + if(filter.empty()) + { + COMDLG_FILTERSPEC cdlgfilter{ L"All Files(*.*)", L"*.*" }; + fileDialog->SetFileTypes(1, &cdlgfilter); + } + else + { + auto cdlgfilter = std::make_unique(filter.size()); + for (size_t i = 0; i < filter.size(); ++i) + { + cdlgfilter[i].pszName = filter[i].description.c_str(); + cdlgfilter[i].pszSpec = filter[i].extension.c_str(); + } + fileDialog->SetFileTypes((UINT)filter.size(), cdlgfilter.get()); + } + //设置Flag + FILEOPENDIALOGOPTIONS options; + fileDialog->GetOptions(&options); + fileDialog->SetOptions(options | flags); + fileDialog->SetDefaultExtension(defExtName.data()); + hr = fileDialog->Show((HWND)parentWndHandle); + if (FAILED(hr)) + { + fileDialog->Release(); + return false; + } + if (multiple && isOpen) + { + IShellItemArray* pItems = nullptr; + static_cast(fileDialog)->GetResults(&pItems); + + if (!pItems) + { + fileDialog->Release(); + return false; + } + + DWORD itemCount; + pItems->GetCount(&itemCount); + selectedFiles.reserve(itemCount); + for (DWORD i = 0; i < itemCount; ++i) + { + IShellItem* pItem = nullptr; + pItems->GetItemAt(i, &pItem); + if (!pItem) + continue; + + PWSTR pszFilePath; + pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath); + selectedFiles.emplace_back(pszFilePath); + CoTaskMemFree(pszFilePath); + pItem->Release(); + } + pItems->Release(); + } + else + { + IShellItem* pItems = nullptr; + fileDialog->GetResult(&pItems); + if (!pItems) + { + fileDialog->Release(); + return false; + } + PWSTR pszFilePath; + pItems->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath); + selectedFiles.emplace_back(pszFilePath); + CoTaskMemFree(pszFilePath); + pItems->Release(); + } + fileDialog->Release(); + return true; +#else +#error __TODO__ +#endif + } + + UIResource MReadFile(std::wstring_view filepath, bool string) + { + FILE* file = MOpenFile(filepath, "rb"); + if (!file) return {}; + + _fseeki64(file, 0L, SEEK_END); + auto len = _ftelli64(file); + + if(len == -1) + { + fclose(file); + MErrorThrow(MErrorCode::IOError); + } + + rewind(file); + //读文本长度+1 结尾添加结束符 + _m_byte* data = new(std::nothrow) _m_byte[string ? size_t(len + 1) : len]; + if (!data) + MErrorThrow(MErrorCode::OutOfMemory, len); + + if (string) data[len] = '\0'; + len = (_m_ulong)fread(data, 1, len, file); + + fclose(file); + return { data, (_m_size)len }; + } + + _m_size MWriteFile(std::wstring_view filepath, UIResource res) + { + FILE* file = MOpenFile(filepath, "wb"); + if (!file) return 0; + + auto len = fwrite(res.data, 1, res.size, file); + fclose(file); + return len; + } + + FILE* MOpenFile(std::wstring_view filepath, const char* mode) + { + FILE* file = nullptr; + _m_error err = 0; +#ifdef _WIN32 + err = _wfopen_s(&file, filepath.data(), Helper::M_StringToWString(mode).c_str()); +#elif __ANDROID__ + file = fopen64((char*)M_WStringToString(path).c_str(), mode); +#else +#error __TODO__ +#endif + if (!file && err != 0) + MErrorThrow(MErrorCode::IOError, err); + return file; + } + + void MSeekFile(FILE* file, _m_long64 offset, int origin) + { + int err = 0; +#ifdef _WIN32 + err = _fseeki64(file, offset, origin); +#elif __ANDROID__ + err = fseeko64(file, (_m_size)offset, origin); +#endif + if (err != 0) + { + if (err == -1) + MErrorThrow(MErrorCode::InvalidParameter); + else + MErrorThrow(MErrorCode::IOError, err); + } + } + + std::wstring MGetFileName(std::wstring_view path) + { + std::wstring::size_type pos = path.find_last_of('\\'); + if (pos == std::wstring::npos) + pos = path.find_last_of('/'); + pos += 1; + return path.substr(pos, path.length() - pos).data(); + } + + void MEnumDirectory(std::wstring_view path, std::vector& dirlist, bool recursive) try + { + std::filesystem::path _path(path); + if(recursive) + { + for (const auto& entry : std::filesystem::recursive_directory_iterator(_path)) + { + if (!entry.is_directory()) continue; + dirlist.push_back(entry.path().wstring()); + } + } + else + { + for (const auto& entry : std::filesystem::directory_iterator(_path)) + { + if (!entry.is_directory()) continue; + dirlist.push_back(entry.path().wstring()); + } + } + } + CATCH_FSERROR() + + void MEnumFiles(std::wstring_view path, const std::vector& filter, + std::vector& dirlist, bool recursive) try + { + std::filesystem::path _path(path); + auto checkext = [&](std::wstring_view ext) + { + if (filter.empty()) return true; + return std::any_of(filter.begin(), filter.end(), + [&](const std::wstring& str) { return str == ext; }); + }; + + if (recursive) + { + for (const auto& entry : std::filesystem::recursive_directory_iterator(_path)) + { + if (!entry.is_regular_file() || !checkext(entry.path().extension().wstring())) continue; + dirlist.push_back(entry.path().wstring()); + } + } + else + { + for (const auto& entry : std::filesystem::directory_iterator(_path)) + { + if (!entry.is_regular_file() || !checkext(entry.path().extension().wstring())) continue; + dirlist.push_back(entry.path().wstring()); + } + } + } + CATCH_FSERROR() + + bool MRemoveFile(std::wstring_view path) try + { + std::filesystem::path file(path); + return std::filesystem::remove(file); + } + CATCH_FSERROR(false) + + bool MRemoveDirectory(std::wstring_view path, bool removeAll) try + { + std::filesystem::path file(path); + if(removeAll) + return std::filesystem::remove_all(file); + try + { + if (std::filesystem::directory_iterator(file) != std::filesystem::directory_iterator()) + return false; + } + catch (const std::filesystem::filesystem_error&) + { + return false; + } + return std::filesystem::remove(file); + } + CATCH_FSERROR(false) + + bool MFileExists(std::wstring_view path, bool isdir) try + { + std::filesystem::path file(path); + bool exists = std::filesystem::exists(file); + if (!exists) + return false; + bool isDir = std::filesystem::is_directory(file); + if (isdir && !isDir) + return false; + if (!isdir && isDir) + return false; + return true; + } + CATCH_FSERROR(false) + + bool MCreateDirectory(std::wstring_view path, bool mult) try + { + if (MFileExists(path, true)) return true; + + std::filesystem::path file(path); + if (mult) + return std::filesystem::create_directories(file); + return std::filesystem::create_directory(file); + } + catch (const std::filesystem::filesystem_error& ex) + { + if (ex.code() == std::errc::permission_denied) MErrorThrow(MErrorCode::AccessDenied); + else if (ex.code() == std::errc::io_error) MErrorThrow(MErrorCode::IOError); + return false; + } + + std::wstring MGetCurrentDir() + { +#ifdef _WIN32 + wchar_t sPath[MAX_PATH]; + GetModuleFileNameW(nullptr, sPath, MAX_PATH); + std::wstring path = sPath; + path = path.substr(0, path.rfind(L'\\')); + + return path; +#else +#error __TODO__ +#endif + } +} diff --git a/MiaoUI/src/source/Manager/Mui_ControlMgr.cpp b/MiaoUI/src/source/Manager/Mui_ControlMgr.cpp new file mode 100644 index 0000000..4232be1 --- /dev/null +++ b/MiaoUI/src/source/Manager/Mui_ControlMgr.cpp @@ -0,0 +1,239 @@ +/** + * FileName: Mui_ControlMgr.cpp + * Note: UI控件管理器实现 + * + * Copyright (C) 2022 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-3-8 Create +*/ + +#include +//MiaoUI 控件 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Mui::CtrlMgr +{ + + using namespace Ctrl; + + struct RegData + { + RegMethod method; + std::vector resAttrib; + }; + + bool g_isRegMui = false; + std::unordered_map g_regList; + + bool RegisterControl(std::wstring_view name, RegMethod&& method, const std::vector& ptrAttrib) + { + auto iter = g_regList.find(name.data()); + if (iter != g_regList.end()) + return false; + g_regList.insert(std::make_pair(name, RegData{ method, ptrAttrib })); + return true; + } + + void RegisterMuiControl() + { + if (!g_isRegMui) + { + UIButton::Register(); + UICheckBox::Register(); + UIComBox::Register(); + UIControl::Register(); + UIEditBox::Register(); + UIImgBox::Register(); + UILabel::Register(); + UIListBox::Register(); + UIProgressBar::Register(); + UIScroll::Register(); + UISlider::Register(); + UIColorPicker::Register(); + UINavBar::Register(); + } + } + + UIControl* CreateControl(std::wstring_view name, UIControl* parent) + { + UIControl* ret = nullptr; + auto iter = g_regList.find(name.data()); + if (iter != g_regList.end() && iter->second.method) + { + ret = iter->second.method(parent); + } + return ret; + } + + AttribType::type GetAttributeType(std::wstring_view name, std::wstring_view attribName) + { + auto iter = g_regList.find(name.data()); + if (iter != g_regList.end()) + { + for (auto& type : iter->second.resAttrib) + { + if (type.attribName == attribName) + return type.attribType; + } + } + return AttribType::defaults; + } + +#define DEF_ConverGet [](std::wstring_view value) -> std::any +#define DEF_ConverSet [](std::any value) -> std::wstring +#define DEF_NumberConver(type, func) AddMethod( \ + DEF_ConverGet{ return std::make_any(func(value.data())); }, \ + DEF_ConverSet{ return std::to_wstring(std::any_cast(value)); } \ + ) +#define DEF_MPtrConver(type) AddMethod( \ + DEF_ConverGet{ return std::make_any(type((type::Type*)std::stoull(value.data()))); }, \ + DEF_ConverSet{ return std::to_wstring((_m_ptrv)std::any_cast(value).get()); } \ + ) + + std::unordered_map AttribConverter::m_methodList; + + template + std::any AttribInt_ConvGetMethod(std::wstring_view value) + { + std::vector dstValue; + Helper::M_GetAttribValueInt(value, dstValue, _count); + if constexpr (_count == 4) + { + return std::make_any(T{ dstValue[0], dstValue[1], dstValue[2], dstValue[3] }); + } + else + return std::make_any(T{ dstValue[0], dstValue[1] }); + } + + template + std::wstring RectInt_ConvSetMethod(std::any value) + { + auto rect = std::any_cast(value); + return Attrib::Value_Make4x(rect.left, rect.top, rect.right, rect.bottom); + } + + AttribConverter::AttribConverter() + { + //bool + AddMethod( + DEF_ConverGet{ return std::make_any(value == L"true"); }, + DEF_ConverSet{ return std::any_cast(value) ? L"true" : L"false"; } + ); + //float + DEF_NumberConver(float, std::stof); + //double + DEF_NumberConver(double, std::stod); + //wstring + AddMethod( + DEF_ConverGet{ return std::make_any(value); }, + DEF_ConverSet{ return std::any_cast(value); } + ); + //wstring_view + AddMethod( + DEF_ConverGet{ return std::make_any(value); }, + DEF_ConverSet{ return std::any_cast(value).data(); } + ); + //wchar_t + AddMethod( + DEF_ConverGet{ return std::make_any(value[0]);}, + DEF_ConverSet{ std::wstring str; str = std::any_cast(value); return str; } + ); + + //_m_uchar, _m_byte, uint_8 + DEF_NumberConver(_m_uchar, (_m_uchar)std::stoi); + //_m_short, int_16 + DEF_NumberConver(_m_short, (_m_short)std::stoi); + //_m_ushort + DEF_NumberConver(_m_ushort, (_m_ushort)std::stoi); + //_m_int, _m_long, int_32 + DEF_NumberConver(_m_int, (_m_int)std::stoi); + //_m_uint, _m_ulong, uint_32 + DEF_NumberConver(_m_uint, (_m_uint)std::stoul); + //_m_param, _m_long64, int_64 + DEF_NumberConver(_m_param, std::stoll); + //_m_ulong64, _m_size, _m_ptrv, uint_64 + DEF_NumberConver(_m_ulong64, std::stoull); + + //void*, _m_ptr + AddMethod( + DEF_ConverGet{ return std::make_any((void*)std::stoull(value.data())); }, + DEF_ConverSet{ return std::to_wstring((_m_ptrv)std::any_cast(value)); } + ); + + //UIStyle* + AddMethod( + DEF_ConverGet{ return std::make_any((UIStyle*)std::stoull(value.data())); }, + DEF_ConverSet{ return std::to_wstring((_m_ptrv)std::any_cast(value)); } + ); + + //UIBitmap* + AddMethod( + DEF_ConverGet{ return std::make_any((UIBitmap*)std::stoull(value.data())); }, + DEF_ConverSet{ return std::to_wstring((_m_ptrv)std::any_cast(value)); } + ); + + //_m_color + AddMethod<_m_color>( + DEF_ConverGet{ return std::make_any<_m_color>(Helper::M_GetAttribValueColor(value)); }, + DEF_ConverSet + { + _m_color color = std::any_cast<_m_color>(value); + return Color::M_RGBA_STR(color.r, color.g, color.b, color.a); + } + ); + + //_m_rect + AddMethod<_m_rect>(AttribInt_ConvGetMethod<_m_rect, 4>, RectInt_ConvSetMethod<_m_rect>); + //_m_rect_t + AddMethod<_m_rect_t>(AttribInt_ConvGetMethod<_m_rect_t, 4>, RectInt_ConvSetMethod<_m_rect_t>); + //UIRect + AddMethod(AttribInt_ConvGetMethod, RectInt_ConvSetMethod); + + //UIPoint + AddMethod( + AttribInt_ConvGetMethod, + DEF_ConverSet + { + auto pt = std::any_cast(value); + return Attrib::Value_Make2x(pt.x, pt.y); + } + ); + //UISize + AddMethod( + AttribInt_ConvGetMethod, + DEF_ConverSet + { + auto size = std::any_cast(value); + return Attrib::Value_Make2x(size.width, size.height); + } + ); + + //UIStylePtr + DEF_MPtrConver(UIStylePtr); + //UIBitmapPtr + DEF_MPtrConver(UIBitmapPtr); + } + } diff --git a/MiaoUI/src/source/Manager/Mui_ResourceMgr.cpp b/MiaoUI/src/source/Manager/Mui_ResourceMgr.cpp new file mode 100644 index 0000000..12cad64 --- /dev/null +++ b/MiaoUI/src/source/Manager/Mui_ResourceMgr.cpp @@ -0,0 +1,599 @@ +/** + * FileName: Mui_ResourceMgr.cpp + * Note: UI资源管理器 + * + * Copyright (C) 2022-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2023-12-7 Create +*/ +#include +#include +#include +#include +#include + +namespace Mui +{ + using namespace Helper; + + UIResourceMgr::~UIResourceMgr() + { + m_sharedBmpList.clear(); + for(auto& file : m_resList) + { + FreeResFile(file.file); + } + } + + bool UIResourceMgr::AddResourcePath(std::wstring_view path, std::wstring_view key) + { + DMResources* dmres = new DMResources(); + if(dmres->LoadResource(path.data(), false)) + { + m_resList.emplace_back(resfile{ std::make_pair(dmres, key.data()), path.data() }); + return true; + } + delete dmres; + return false; + } + + bool UIResourceMgr::AddResourceMem(UIResource memfile, std::wstring_view key) + { + DMResources* dmres = new DMResources(); + if (dmres->LoadResource(memfile)) + { + m_resList.emplace_back(resfile{ std::make_pair(dmres, key.data()), L"" }); + return true; + } + delete dmres; + return false; + } + + bool UIResourceMgr::RemoveResource(std::wstring_view path) + { + if (path.empty()) + return false; + for(auto iter = m_resList.begin(); iter != m_resList.end(); ++iter) + { + if (iter->path != path) + continue; + FreeResFile(iter->file); + m_resList.erase(iter); + return true; + } + return false; + } + + UIStyle* UIResourceMgr::FindStyle(std::wstring_view name) + { + if (auto iter = m_styleList.find(name.data()); iter != m_styleList.end()) + return iter->second.style.get(); + return nullptr; + } + + void UIResourceMgr::LoadStyleList() + { + for(auto& file : m_resList) + { + std::vector list; + file.file.first->EnumResourceName(list); + for (const auto& name : list) + { + //v1通过名称前缀来确定类型 + if (name.substr(0, 6) != L"style_") + continue; + LoadStyleInternal(file.file, name, nullptr); + } + } + } + + bool UIResourceMgr::LoadStyle(std::wstring_view resname, UIStyle** dst) + { + for (auto& file : m_resList) + { + std::vector list; + file.file.first->EnumResourceName(list); + for (const auto& name : list) + { + if(name == resname) + return LoadStyleInternal(file.file, name, dst); + } + } + return false; + } + + bool UIResourceMgr::AddImageStyle(std::wstring_view name, UIResource memimg, _m_ushort count, + UIStyle** dst, bool nineGrid, _m_rect_t gridpath) + { + StyleData data; + data.memres = true; + if(AddImageStyleInternal(memimg, count, &data.style, nineGrid, gridpath)) + { + auto ret = m_styleList.insert(std::make_pair(name.data(), data)); + if (!ret.second) + { + data.style.rest(); + return false; + } + if (dst) + *dst = data.style.get(); + return true; + } + return false; + } + + bool UIResourceMgr::AddGeometryStyle(std::wstring_view name, std::wstring_view xml, UIStyle** dst) + { + StyleData data; + data.memres = false; + if (AddGeometryStyleInternal(xml, &data.style)) + { + auto ret = m_styleList.insert(std::make_pair(name.data(), data)); + if (!ret.second) + { + data.style.rest(); + return false; + } + if (dst) + *dst = data.style.get(); + return true; + } + return false; + } + + UIStyle* UIResourceMgr::RemoveStyle(std::wstring_view resname) + { + auto iter = m_styleList.find(resname.data()); + if (iter != m_styleList.end()) + { + auto style = iter->second.style; + style->AddRef(); + m_styleList.erase(iter); + return style.get(); + } + return nullptr; + } + + UIResource UIResourceMgr::ReadResource(std::wstring_view name) + { + for (auto& file : m_resList) + { + UIResource res = file.file.first->ReadResource(name.data(), file.file.second, DataRes).res; + if (res.data) + return res; + } + return {}; + } + + UIBitmapPtr UIResourceMgr::CreateUniqueUIBitamp(std::wstring_view name) + { + auto res = ReadResource(name); + auto ret = CreateUniqueUIBitamp(res); + res.Release(); + return ret; + } + + UIBitmapPtr UIResourceMgr::CreateUniqueUIBitamp(UIResource res) + { + if (!res) return nullptr; + + const auto bitmap = m_render->CreateBitmap(res); + if (!bitmap) return nullptr; + + const auto bmp = new UIBitmap(); + bmp->m_bitmap = bitmap; + + return bmp; + } + + UIBitmapPtr UIResourceMgr::CreateSharedUIBitmap(std::wstring_view name) + { + auto iter = m_sharedBmpList.find(name.data()); + if (iter != m_sharedBmpList.end()) + return iter->second; + + auto res = ReadResource(name); + if (!res) return nullptr; + + const auto bitmap = m_render->CreateBitmap(res); + res.Release(); + if (!bitmap) return nullptr; + + const auto ret = new UIBitmap(); + ret->m_bitmap = bitmap; + + m_sharedBmpList[name.data()] = ret; + + return ret; + } + + bool UIResourceMgr::LoadStyleFromDMRes(MUIRESFILE& file, std::wstring_view name, std::wstring& dst, bool res, UIResource* dstres) + { + auto reskey = file.first->ReadResource(name.data(), file.second, res ? AllRes : StringRes); + if ((reskey.res.data || reskey.res.size == 0) && reskey.resText.empty()) + { + reskey.res.Release(); + return false; + } + if (res) + *dstres = reskey.res; + dst = reskey.resText; + dst = M_ReplaceString(dst, L"\r\n", L"\n"); + dst = M_ReplaceString(dst, L"\r", L"\n"); + + return true; + } + + bool UIResourceMgr::LoadStyleInternal(MUIRESFILE& file, std::wstring_view name, UIStyle** dststyle) + { + std::wstring dst; + UIResource res; + if (!LoadStyleFromDMRes(file, name, dst, true, &res)) + return false; + + auto clean = RAII::scope_exit([&] { res.Release(); }); + + auto lineCount = M_GetTextCount(dst, L"\n") + 1; + + if (lineCount < 2) + { + res.Release(); + return false; + } + + /* StyleImage + * 1 - 类型 + * 4 - count + * 3,3,3,3 - path + * + * StyleGeometry + * 2 - 类型 + * xml .... + */ + + auto type = M_StoInt(M_GetTextLine(dst, 1)); + if (type < 1 || type > 2) + return false; + + StyleData data; + data.memres = false; + data.srcname = name; + + if (type == 1) + { + auto count = (_m_ushort)M_StoInt(M_GetTextLine(dst, 2)); + if (count < 1) + return false; + + bool nineGrid = false; + _m_rect_t gridpath = { 0 }; + if (lineCount > 2) + { + nineGrid = true; + std::vector dstValue; + M_GetAttribValueInt(M_GetTextLine(dst, 3), dstValue, 4); + gridpath = { dstValue[0], dstValue[1], dstValue[2], dstValue[3] }; + } + if (!AddImageStyleInternal(res, count, &data.style, nineGrid, gridpath)) + return false; + } + else if (type == 2) + { + auto start = dst.find('\n'); + dst = dst.substr(start, dst.length() - start); + if (!AddGeometryStyleInternal(dst, &data.style)) + return false; + } + //重名 + if (auto [iter, success] = m_styleList.insert(std::make_pair(name, data)); !success) + { + data.style.rest(); + return false; + } + if (dststyle) + *dststyle = data.style.get(); + return true; + } + + bool UIResourceMgr::AddImageStyleInternal(UIResource memimg, _m_ushort count, + UIStyle** dst, bool nineGrid, _m_rect_t gridpath) + { + auto bmp = CreateUniqueUIBitamp(memimg); + if (!bmp) + return false; + + UIStyleImage* ret = new UIStyleImage(); + ret->m_bitmap = bmp; + ret->m_nineGrid = nineGrid; + ret->m_gridPath = gridpath; + ret->m_partCount = count; + ret->AddRef(); + *dst = ret; + + return true; + } + + bool UIResourceMgr::AddGeometryStyleInternal(std::wstring_view xml, UIStyle** dst) + { + pugi::xml_document doc; + std::wstring _xml = xml.data(); + _xml += L"" + _xml + L""; + if (!doc.load_string(_xml.c_str())) + return false; + + auto root = doc.child(L"root"); + if (root.empty()) + return false; + + UIStyleGeometry* style = nullptr; + int count = 0; + + //解析指令 + static auto ParseCMD = [](UIStyleGeometry::CMD& data, const pugi::xml_node& node) + { + std::wstring_view name = node.name(); + if (name == L"fill_rect") data.type = 0; + else if (name == L"fill_round") data.type = 1; + else if (name == L"draw_rect") data.type = 2; + else if (name == L"draw_round") data.type = 3; + else if (name == L"draw_line") data.type = 4; + else if (name == L"fill_ellipse") data.type = 5; + else if (name == L"draw_ellipse") data.type = 6; + else return false; + + for (auto attrib : node.attributes()) + { + name = attrib.name(); + if (name == L"rc") + { + std::vector value; + M_GetAttribValue(attrib.value(), value, 4); + for (size_t i = 0; i < 4; ++i) + { + std::wstring& v = value[i]; + if (v.empty()) + continue; + if (v[0] == L'l' || v[0] == L't' || v[0] == L'r' || v[0] == L'b') + { + switch (v[0]) + { + case L'l': + data.m_rctype[i] = 1; + break; + case L't': + data.m_rctype[i] = 2; + break; + case L'r': + data.m_rctype[i] = 3; + break; + case L'b': + data.m_rctype[i] = 4; + break; + default: + data.m_rctype[i] = 0; + break; + } + v = v.substr(1, v.length() - 1); + } + const int num = M_StoInt(v); + switch (i) + { + case 0: + data.m_dst.left = num; + break; + case 1: + data.m_dst.top = num; + break; + case 2: + data.m_dst.right = num; + break; + case 3: + data.m_dst.bottom = num; + break; + default: + break; + } + } + } + else if (name == L"color") + data.color = M_GetAttribValueColor(attrib.value()); + else if (name == L"value") + data.param = M_StoFloat(attrib.value()); + else if (name == L"width") + data.width = M_StoInt(attrib.value()); + } + return true; + }; + + for (auto& node : root.children()) + { + if (const std::wstring_view name = node.name(); name != L"part") + continue; + + std::vector cmdList; + for (auto& cmd : node.children()) + { + UIStyleGeometry::CMD data; + if (!ParseCMD(data, cmd)) + continue; + cmdList.push_back(data); + } + + if (!style) + style = new UIStyleGeometry(); + + style->m_cmdlist.push_back(std::move(cmdList)); + + count++; + } + if(style) + { + style->m_partCount = (_m_ushort)count; + style->AddRef(); + style->InitResource(m_render); + if (dst) + *dst = style; + return true; + } + return false; + } + + void UIResourceMgr::FreeResFile(MUIRESFILE& file) + { + delete file.first; + file.first = nullptr; + } + + void UIStyleImage::PaintStyle(Render::MRenderCmd* render, MPCRect dest, _m_byte alpha, + int state, _m_ushort count, _m_scale scale) + { + if (!m_bitmap) + return; + + auto bitmap = m_bitmap->GetBitmap(); + + UISize styleSize = bitmap->GetSize(); + styleSize.width /= m_partCount; + + if (m_partCount == count) + { + _m_rect rect_ = _m_rect(styleSize.width * state, 0, styleSize.width * (state + 1), styleSize.height); + if (state == 0) + rect_.left = 0; + if (!m_nineGrid) + render->DrawBitmap(bitmap, alpha, *dest, rect_); + else + { + render->DrawNinePalacesImg(bitmap, alpha, *dest, rect_, m_gridPath); + } + } + else + MErrorThrow(MErrorCode::InvalidParameter); + } + + void UIStyleGeometry::PaintStyle(Render::MRenderCmd* render, MPCRect dest, _m_byte alpha, + int state, _m_ushort count, _m_scale scale) + { + if(m_partCount != count) + { + MErrorThrow(MErrorCode::InvalidParameter); + return; + } + if (m_cmdlist.size() != count) return; + + auto CalcValue = [&](_m_byte type, _m_byte rctype, int src) + { + int value; + bool add = rctype < 2; + switch (rctype) + { + case 1: //top + value = dest->top; + break; + case 2: //right + value = dest->right; + break; + case 3: //bottom + value = dest->bottom; + break; + default://left + value = dest->left; + break; + } + + switch (type) + { + case 1://left + return dest->left - src; + case 2://top + return dest->top - src; + case 3://right + return dest->right - src; + case 4://bottom + return dest->bottom - src; + case 0: + default: + if (add) + return value + src; + return value - src; + } + }; + + for (auto& cmd : m_cmdlist[state]) + { + _m_rect dstrc; + dstrc.left = CalcValue(cmd.m_rctype[0], 0, _scale_to(cmd.m_dst.left, scale.cx)); + dstrc.top = CalcValue(cmd.m_rctype[1], 1, _scale_to(cmd.m_dst.top, scale.cy)); + dstrc.right = CalcValue(cmd.m_rctype[2], 2, _scale_to(cmd.m_dst.right, scale.cx)); + dstrc.bottom = CalcValue(cmd.m_rctype[3], 3, _scale_to(cmd.m_dst.bottom, scale.cy)); + + if (cmd.type == 0 || cmd.type == 1 || cmd.type == 5) + { + m_brush->SetColor(cmd.color); + m_brush->SetOpacity(alpha); + } + else if (cmd.type == 2 || cmd.type == 3 || cmd.type == 4 || cmd.type == 6) + { + m_pen->SetColor(cmd.color); + m_pen->SetWidth(_scale_to(cmd.width, scale.cx)); + m_pen->SetOpacity(alpha); + } + switch (cmd.type) + { + case 0: + render->FillRectangle(dstrc, m_brush); + break; + case 1: + render->FillRoundedRect(dstrc, _scale_to(cmd.param, M_MIN(scale.cx, scale.cy)), m_brush); + break; + case 2: + render->DrawRectangle(dstrc, m_pen); + break; + case 3: + render->DrawRoundedRect(dstrc, _scale_to(cmd.param, M_MIN(scale.cx, scale.cy)), m_pen); + break; + case 4: + render->DrawLine({ dstrc.left, dstrc.top }, { dstrc.right, dstrc.bottom }, m_pen); + break; + case 5: + render->FillEllipse(dstrc, m_brush); + break; + case 6: + render->DrawEllipse(dstrc, m_pen); + break; + default: + break; + } + } + } + + void UIStyleGeometry::InitResource(Render::MRenderCmd* render) + { + for (auto& part : m_cmdlist) + { + for (const auto& cmd : part) + { + //创建资源 + if (!m_brush && (cmd.type == 0 || cmd.type == 1 || cmd.type == 5)) + m_brush = render->CreateBrush(Color::M_None); + else if (!m_pen && (cmd.type == 2 || cmd.type == 3 || cmd.type == 4 || cmd.type == 6)) + m_pen = render->CreatePen(1, Color::M_None); + + if (m_brush && m_pen) break; + } + if (m_brush && m_pen) break; + } + } +} diff --git a/MiaoUI/src/source/Mui_Base.cpp b/MiaoUI/src/source/Mui_Base.cpp new file mode 100644 index 0000000..60ef75a --- /dev/null +++ b/MiaoUI/src/source/Mui_Base.cpp @@ -0,0 +1,459 @@ +/** + * FileName: Mui_Base.cpp + * Note: UI基本类型定义 + * + * Copyright (C) 2020-2022 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-17 Create +*/ + +#include +#include +#include +#include +#include + +using namespace std::chrono; + +#ifdef __ANDROID__ +#include +#endif + +namespace Mui +{ + +#pragma region _m_color + + _m_color::_m_color(std::wstring_view hex, bool alpha) : argb(0) + { + std::wstringstream ss; + ss << std::hex << std::setw(8) << hex; + ss >> argb; + if (!alpha) + { + a = 255; + return; + } + std::swap(a, r); + std::swap(b, g); + } + + std::wstring _m_color::HEX(bool alpha) + { + std::wstringstream ss; + ss << std::hex << std::setw(2) << std::setfill(L'0') << r + << std::hex << std::setw(2) << std::setfill(L'0') << g + << std::hex << std::setw(2) << std::setfill(L'0') << b; + + if (alpha) + ss << std::hex << std::setw(2) << std::setfill(L'0') << a; + + return ss.str(); + } + + _m_byte _m_color::AlphaBlend(_m_byte dst, _m_byte src) + { + return (_m_byte)((float)dst * (float)src / 255.f); + } + +#pragma endregion + + +#pragma region UIRect + + UIRect::UIRect() + { + left = top = right = bottom = 0; + } + + UIRect::UIRect(const _m_rect& src) + { + left = (int)src.left; + top = (int)src.top; + right = (int)src.right; + bottom = (int)src.bottom; + } + + UIRect::UIRect(int x, int y, int width, int height) + { + left = x; + top = y; + right = x + width; + bottom = y + height; + } + + _m_rect UIRect::ToRect() const + { + return { left, top, right, bottom }; + } + + void UIRect::SetWidth(int width) + { + right = left + width; + } + + void UIRect::SetHeight(int height) + { + bottom = top + height; + } + + bool UIRect::IsEmpty() const + { + return left == 0 && top == 0 && right == 0 && bottom == 0; + } + + void UIRect::Empty() + { + left = top = right = bottom = 0; + } + + void UIRect::Join(const UIRect& rc) + { + if (rc.left < left) left = rc.left; + if (rc.top < top) top = rc.top; + if (rc.right > right) right = rc.right; + if (rc.bottom > bottom) bottom = rc.bottom; + } + + void UIRect::ResetOffset() + { + Helper::Rect::Offset(this, -offsetx, -offsety); + offsetx = offsety = 0; + } + + void UIRect::Normalize() + { + if (left > right) + { + int old = left; + left = right; + right = old; + } + if (top > bottom) + { + int old = top; + top = bottom; + bottom = old; + } + } + + void UIRect::Offset(int cx, int cy) + { + offsetx += cx; + offsety += cy; + Helper::Rect::Offset(this, cx, cy); + } + + void UIRect::Inflate(int cx, int cy) + { + Helper::Rect::Inflate(this, cx, cy); + } + + void UIRect::Deflate(int cx, int cy) + { + Helper::Rect::Inflate(this, -cx, -cy); + } + + void UIRect::Union(const UIRect& rc) + { + Helper::Rect::Union(this, this, &rc); + } + + bool UIRect::operator==(const UIRect& rc) const + { + return rc.left == left && rc.top == top + && rc.right == right && rc.bottom == bottom; + } + + bool UIRect::operator!=(const UIRect& rc) const + { + return rc.left != left || rc.top != top + || rc.right != right || rc.bottom != bottom; + } + +#pragma endregion + +#pragma region UIFontStyle + + bool UIFontStyle::operator==(const UIFontStyle& font) const + { + return bold == font.bold && italics == font.italics && + underline == font.underline && strikeout == font.strikeout; + } + + bool UIFontStyle::operator!=(const UIFontStyle& font) const + { + return bold != font.bold || italics != font.italics || + underline != font.underline || strikeout != font.strikeout; + } + +#pragma endregion + +#pragma region UIString + + UIString::UIString() + { + CopyForm(L""); + } + + UIString::UIString(_m_lpcwstr pstr) + { + CopyForm(pstr); + } + + UIString::UIString(std::wstring_view strview) : UIString(strview.data()) + { + } + + UIString::UIString(const std::wstring& string) : UIString(string.data()) + { + } + + UIString::UIString(UIString&& other) noexcept + { + std::swap(m_length, other.m_length); + m_string = std::move(other.m_string); + } + + UIString::UIString(const UIString& other) + { + CopyForm(other.m_string.get()); + } + + UIString& UIString::operator=(const UIString& other) + { + if (&other == this) + return *this; + + CopyForm(other.m_string.get()); + + return *this; + } + + UIString& UIString::operator=(UIString&& other) noexcept + { + if (&other == this) + return *this; + std::swap(m_length, other.m_length); + m_string = std::move(other.m_string); + return *this; + } + + UIString::~UIString() = default; + + bool UIString::operator==(_m_lpcwstr other) const + { + return wcscmp(other, m_string.get()) == 0; + } + + bool UIString::operator!=(_m_lpcwstr other) const + { + return !this->operator==(other); + } + + bool UIString::operator==(const UIString& other) const + { + return wcscmp(other.m_string.get(), m_string.get()) == 0; + } + + bool UIString::operator!=(const UIString& other) const + { + return !this->operator==(other); + } + + bool UIString::operator==(std::wstring_view other) const + { + return view() == other; + } + + bool UIString::operator!=(std::wstring_view other) const + { + return !this->operator==(other); + } + + void UIString::CopyForm(_m_lpcwstr pstr) + { + m_string = nullptr; + m_length = wcslen(pstr); + auto string = new wchar_t[m_length + 1]; + memcpy(string, pstr, m_length * sizeof(wchar_t)); + string[m_length] = 0; + m_string.reset(string); + } + +#pragma endregion + +#pragma region MTimer + + MTimers::MTimers() : MThread([this] { ThreadProc(); }) + { + MThread::Start(true); + } + + MTimers::~MTimers() + { + MThread::Stop(); + for(auto& id : m_idList) + { + delete (_m_param*)id.first; + } + m_idList.clear(); + m_timerList.clear(); + } + + MTimers::ID MTimers::AddTimer(_m_uint elapse, CallBack callback) + { + std::lock_guard lock(m_lock); + ID id = (ID)new _m_param; + timerID tid = std::make_pair(GetMilliSeconds() + (_m_param)elapse, id); + m_idList[id] = tid; + timer _timer; + _timer.callback = std::move(callback); + _timer.tid = id; + _timer.elapse = elapse; + m_timerList[tid] = _timer; + Resume(); + return id; + } + + bool MTimers::DelTimer(ID id) + { + std::lock_guard lock(m_lock); + auto p = m_idList.find(id); + if (p == m_idList.end()) + return false; + + delete (_m_param*)id; + m_idList.erase(p); + + for (auto iter = m_timerList.begin(); iter != m_timerList.end(); ++iter) + { + if (iter->second.tid != id) + continue; + m_timerList.erase(iter); + break; + } + return true; + } + + _m_param MTimers::GetMilliSeconds() + { + auto duration = steady_clock::now(); + auto micro = duration_cast(duration.time_since_epoch()).count(); + return _m_param(micro); + } + + _m_param MTimers::GetSleepTime() + { + if (!m_timerList.empty()) + return m_timerList.begin()->first.first - GetMilliSeconds(); + return 0; + } + + void MTimers::ThreadProc() + { + //auto sleepTime = GetSleepTime(); + //if (sleepTime > 0) + if (!IsRuning()) return; + Helper::M_Sleep(1); + + auto micro = GetMilliSeconds(); + while (!m_timerList.empty() && m_timerList.begin()->first.first < micro) + { + + auto iter = m_timerList.begin(); + auto tid = iter->first; + auto timer = iter->second; + + timer.time += timer.elapse;//累加时间 + if (timer.callback) { + timer.callback(timer.tid, timer.time); + } + + std::lock_guard lock(m_lock); + m_timerList.erase(iter); + + //如果用户没有调用DelTimer + auto p = m_idList.find(timer.tid); + //更新计时器时间 + if (p != m_idList.end()) { + tid.first = GetMilliSeconds() + timer.elapse; + m_timerList.insert(std::make_pair(tid, timer)); + } + + micro = GetMilliSeconds(); + } + if(m_timerList.empty()) + Pause(); + } + +#pragma endregion + +#pragma region MFPSCounter + + _m_uint MFPSCounter::CalcFPS() + { + curTime = steady_clock::now(); + + auto duration = duration_cast(curTime - lastTime); + float duration_s = float(duration.count()) * microseconds::period::num / microseconds::period::den; + + if (duration_s > 1)//1秒之后开始统计FPS + { + fps = (float)frameCount / duration_s; + frameCount = 0; + lastTime = curTime; + } + + ++frameCount; + return (_m_uint)fps; + } + + void MFPSCounter::SetMaxFPS(float _fps) + { + if (_fps < 0.f) + fpstime = -1.f; + else { + fpstime = _fps; + m_fpsLimit = std::chrono::duration_cast(std::chrono::duration{ 1.f / fps }); + } + } + + float MFPSCounter::GetMaxFPS() + { + if (fpstime == -1.f) + return -1.f; + return fpstime; + } + + void MFPSCounter::LimitFPS() + { + if (fpstime != -1.0f) + { + auto time_in_seconds = time_point_cast(steady_clock::now()); + ++frame_count_per_second; + if (time_in_seconds > prev_time_in_seconds) + { + frame_count_per_second = 0; + prev_time_in_seconds = time_in_seconds; + } + std::this_thread::sleep_until(m_EndFrame); + m_BeginFrame = m_EndFrame; + m_EndFrame = m_BeginFrame + m_fpsLimit; + } + } + +#pragma endregion +} \ No newline at end of file diff --git a/MiaoUI/src/source/Mui_Debug.cpp b/MiaoUI/src/source/Mui_Debug.cpp new file mode 100644 index 0000000..5fe0da8 --- /dev/null +++ b/MiaoUI/src/source/Mui_Debug.cpp @@ -0,0 +1,171 @@ +/** + * FileName: Mui_Debug.h + * Note: 调试输出Helper + * + * Copyright (C) 2023-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2023-1-19 Create +*/ +#include +#include +#include + +#ifdef _MSC_VER +#include +#define __ADDRESS__ _AddressOfReturnAddress() +#elif __clang__ +#define __ADDRESS__ __builtin_return_address(0) +#else +#error "__ADDRESS__ 没有可用定义" +#endif + +namespace Mui +{ + std::exception_ptr _g_last_merror_exception = nullptr; + std::function _g_exception_callback = nullptr; + + std::string addressFromat(void* pointer = __ADDRESS__) + { + std::stringstream ss; + ss << "0x" << std::setfill('0') << std::setw(sizeof(void*) * 2) << std::hex << reinterpret_cast(pointer); + return ss.str(); + } + +#if (MUI_CFG_ENABLE_DBGSOURCE && MUI_CFG_ENABLE_DBGFUNNAME) + std::wstring _m_dbg_format_(std::string_view __FILE, std::string_view __FUN) + { + auto pos = __FILE.rfind('\\'); + if (pos == std::string::npos) pos = __FILE.rfind('/'); + if (pos == std::string::npos) pos = 0; + else pos++; + std::string str = "["; + str += __FILE.substr(pos, __FILE.length() - pos); + str += "] -> "; + str += __FUN; + return Helper::M_StringToWString(str); + } +#elif (MUI_CFG_ENABLE_DBGSOURCE || MUI_CFG_ENABLE_DBGFUNNAME) + std::wstring _m_dbg_format_(std::string_view __INFO) + { +#if MUI_CFG_ENABLE_DBGSOURCE + auto pos = __INFO.rfind('\\'); + if (pos == std::string::npos) pos = __INFO.rfind('/'); + if (pos == std::string::npos) pos = 0; + else pos++; + std::string str = "["; + str += __INFO.substr(pos, __INFO.length() - pos); + str += "] -> "; + str += addressFromat(); +#else + std::string str = "[" + addressFromat() + "] -> "; + str += __INFO; +#endif + return Helper::M_StringToWString(str); + } +#else + std::wstring _m_dbg_format_() + { + std::string str = "[" + addressFromat() + "]"; + return Helper::M_StringToWString(str); + } +#endif + + void ErrorDialog(std::wstring_view info) + { +#ifdef _WIN32 + MessageBoxW(0, info.data(), L"MiaoUI Library", MB_ICONERROR | MB_OK); +#elif __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "MUIERROR", "%s", M_WStringToString(info).c_str()); +#else +#error __TODO__ +#endif + } + + void _g_last_exception_callback(const MError& ex) + { + std::wstring errinfo = L"很抱歉,MiaoUI抛出了一个错误 :(\n该异常未经处理,程序已被迫终止。\n"; + errinfo += L"I'm sorry, but MiaoUI is throwing an error :(\nThe exception was unhandled and the program is about to be forced to terminate.\n"; + errinfo += L"错误信息(Error message): " + ex.fullText(); + ErrorDialog(errinfo); + } + + void M_OutError(std::wstring_view cls, std::wstring_view error, bool exit) + { +#ifdef _DEBUG + std::wstring str = L"\n"; + str += cls; + str += L" -> "; + str += error; + M_DbgOutInfo(str.c_str()); +#ifdef _WIN32 + _CrtDbgBreak(); +#endif +#else + std::wstring errinfo; + if (!exit) + errinfo = L"该错误为非致命错误 程序将可以继续运行."; + else + errinfo = L"该错误为致命错误 程序无法继续运行."; + + errinfo = L"很抱歉,MiaoUI库内部抛出了一个错误 :(\n" + errinfo + L"\n错误信息:\n"; + errinfo += cls; + errinfo += L" -> "; + errinfo += error; + ErrorDialog(errinfo.c_str()); +#endif + if (exit) + ::exit(1); + } + + void M_OutError(const MError& err, std::wstring_view cls) + { + if (cls.empty()) + M_OutError(L"[Mui_Error.cpp]", err.fullText()); + else + M_OutError(cls, err.fullText()); + } + + void M_SetLastExceptionNotify(const std::function& callback) + { + _g_exception_callback = callback; + std::set_terminate([] + { + if (!_g_last_merror_exception) return; + try + { + std::rethrow_exception(_g_last_merror_exception); + } + catch (const MError& ex) + { + _g_exception_callback(ex); + } + }); + } + + void M_ThreadPostException(std::exception_ptr ex) + { + if (_g_exception_callback) + { + try { std::rethrow_exception(_g_last_merror_exception); } + catch (const MError& ex) + { + _g_exception_callback(ex); + std::abort(); + } + } + else + std::rethrow_exception(ex); + } +} diff --git a/MiaoUI/src/source/Mui_Error.cpp b/MiaoUI/src/source/Mui_Error.cpp new file mode 100644 index 0000000..522febc --- /dev/null +++ b/MiaoUI/src/source/Mui_Error.cpp @@ -0,0 +1,212 @@ +/** + * FileName: Mui_Error.cpp + * Note: 错误信息和类型定义 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-9-18 Create +*/ +#include +#include +#include +#include + +namespace Mui +{ + MError::MError(MErrorCode err, _m_error ext) noexcept + { + m_errCode = err; + m_extCode = ext; + } + + MError::MError(const char* what) noexcept + : std::exception(what) + { + m_errCode = MErrorCode::STDError; + } + + MError::MError(_m_error hresult, std::wstring_view err) + { + m_errCode = MErrorCode::WinCOMError; + m_extCode = hresult; + m_extInfo = err; + } + + MError::MError(std::wstring_view info) + { + m_errCode = MErrorCode::MUIError; + m_extInfo = info; + } + + const char* MError::what() const noexcept + { + if (auto err = exception::what(); m_errCode == MErrorCode::STDError && err) + return err; + return toMessage(m_errCode); + } + + std::wstring MError::whatW() const + { + if (auto err = exception::what(); m_errCode == MErrorCode::STDError && err) + return Helper::M_StringToWString(err); + if (m_errCode == MErrorCode::WinCOMError || m_errCode == MErrorCode::MUIError) + return m_extInfo; + return toMessageW(m_errCode); + } + + MErrorCode MError::errCode() const noexcept + { + return m_errCode; + } + + _m_error MError::extCode() const noexcept + { + return m_extCode; + } + + const char* MError::toMessage(MErrorCode errCode) + { + switch (errCode) + { + case MErrorCode::OK: + return "OK."; + case MErrorCode::False: + return "Failed."; + case MErrorCode::InvalidParameter: + return "Invalid parameters, one or more parameters are invalid."; + case MErrorCode::NotSupported: + return "The current operation or function is not supported."; + case MErrorCode::InvalidData: + return "Invalid data, or incorrect formatting."; + case MErrorCode::DeviceLost: + return "The device has been lost or its status has changed, discard the associated device resource and recreate it."; + case MErrorCode::InternalDeviceError: + return "Internal device error, please check the relevant device information or driver."; + case MErrorCode::OutOfMemory: + return "Memory allocation failed, or there is not enough memory."; + case MErrorCode::IndexOutOfRange: + return "The index is outside the specified array or prescribed range."; + case MErrorCode::TheThreadIsBusy: + return "The thread is currently busy, please wait or visit later."; + case MErrorCode::BufferTooSmall: + return "The buffer is too small, please consider increasing it."; + case MErrorCode::AccessDenied: + return "Access denied."; + case MErrorCode::IOError: + return "I/O error, or file system error."; + case MErrorCode::OutdatedInterface: + return "Outdated interfaces are not recommended, but if you want to use them, add the appropriate compilation flags."; + case MErrorCode::RenderInternalError: + return "There is an abnormality inside the renderer. Please check the implementation of the rendering interface."; + case MErrorCode::STDError: + return "C++ standard library exception, use what() to retrieve information."; + case MErrorCode::WinCOMError: + return "Windows COM HRESULT error code, please check extCode."; + case MErrorCode::MUIError: + return "MUI other errors, use whatW() to get detailed information."; + } + return "Unknown error"; + } + + std::wstring MError::toMessageW(MErrorCode errCode) + { + std::wstring ret; + switch (errCode) + { + case MErrorCode::OK: + ret = L"成功。"; + break; + case MErrorCode::False: + ret = L"已失败。"; + break; + case MErrorCode::InvalidParameter: + ret = L"无效的参数,一个或多个参数无效。"; + break; + case MErrorCode::NotSupported: + ret = L"不支持当前的操作或者功能。"; + break; + case MErrorCode::InvalidData: + ret = L"无效的数据,或格式不正确。"; + break; + case MErrorCode::DeviceLost: + ret = L"设备已丢失或状态发生改变,请丢弃关联的设备资源然后重新创建。"; + break; + case MErrorCode::InternalDeviceError: + ret = L"设备内部错误,请检查相关的设备信息或者驱动程序。"; + break; + case MErrorCode::OutOfMemory: + ret = L"内存分配失败,或内存不足。"; + break; + case MErrorCode::IndexOutOfRange: + ret = L"索引超出指定的数组或规定范围。"; + break; + case MErrorCode::TheThreadIsBusy: + ret = L"线程当前正忙,请等待或稍后再访问。"; + break; + case MErrorCode::BufferTooSmall: + ret = L"缓冲区过小,请考虑增大。"; + break; + case MErrorCode::AccessDenied: + ret = L"权限不足。"; + break; + case MErrorCode::IOError: + ret = L"I/O 错误,或文件系统错误。"; + break; + case MErrorCode::OutdatedInterface: + ret = L"已过时的接口,不建议使用,若要使用 请添加相应的编译Flag."; + break; + case MErrorCode::RenderInternalError: + ret = L"渲染器内部异常,请检查渲染器接口实现。"; + break; + case MErrorCode::STDError: + ret = L"C++标准库异常,使用what()获取信息。"; + break; + case MErrorCode::WinCOMError: + ret = L"Windows COM HRESULT错误代码,请检查extCode。"; + break; + case MErrorCode::MUIError: + ret = L"MUI其他错误,使用whatW()获取详细信息。"; + break; + case MErrorCode::Unknown: + default: + ret = L"未知错误。"; + break; + } + + ret += L"\n(" + Helper::M_StringToWString(toMessage(errCode)) + L")"; + return ret; + } + + std::wstring MError::fullText() const + { + std::wstringstream stream; + stream << std::hex << std::uppercase << std::setfill(L'0') << std::setw(2); + + if (m_errCode == MErrorCode::WinCOMError) + stream << (_m_long)m_extCode; + else + stream << (_m_error)m_errCode; + + std::wstring ret = L" (0x" + stream.str() + L"): "; + ret += whatW(); + + if (m_errCode == MErrorCode::WinCOMError) + ret = L"HRESUL COM ErrCode" + ret; + else + ret = L"MiaoUI ErrCode" + ret; + + return ret; + } +} diff --git a/MiaoUI/src/source/Mui_Helper.cpp b/MiaoUI/src/source/Mui_Helper.cpp new file mode 100644 index 0000000..dd007cd --- /dev/null +++ b/MiaoUI/src/source/Mui_Helper.cpp @@ -0,0 +1,393 @@ +/** + * FileName: Mui_Helper.cpp + * Note: 界面库助手函数 + * + * Copyright (C) 2023-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2023-5-11 Create +*/ +#include + +#include + +#ifdef _WIN32 +#pragma comment(lib, "winmm.lib") +#endif // _WIN32 + +namespace Mui::Helper +{ + int M_CalcAspectRatio(int fromWidth, int fromHeight, int toWidthOrHeight, bool isWidth) + { + if (isWidth) + return (int)round((float)fromHeight * ((float)toWidthOrHeight / (float)fromWidth)); + return (int)round((float)fromWidth * ((float)toWidthOrHeight / (float)fromHeight)); + } + + std::string M_WStringToString(std::wstring_view width) + { +#ifdef _WIN32 + int len = WideCharToMultiByte(CP_ACP, 0, width.data(), (int)width.size(), NULL, 0, NULL, NULL); + char* buffer = new char[len + 1]; + WideCharToMultiByte(CP_ACP, 0, width.data(), (int)width.size(), buffer, len, NULL, NULL); + buffer[len] = '\0'; + std::string ret = buffer; + delete[] buffer; + return ret; +#elif __ANDROID__ + auto len = width.length(); + char* buffer = new char[len + 1]; + wcstombs(buffer, width.data(), len); + buffer[len] = '\0'; + std::string ret = buffer; + ret = ret.substr(0, len); + delete[] buffer; + return ret; +#else +#error __TODO__ + return std::string(); +#endif // _WIN32 + } + + std::wstring M_StringToWString(std::string_view str) + { +#ifdef _WIN32 + int len = MultiByteToWideChar(CP_ACP, 0, str.data(), (int)str.size(), NULL, 0); + wchar_t* buffer = new wchar_t[len + 1]; + MultiByteToWideChar(CP_ACP, 0, str.data(), (int)str.size(), buffer, len); + buffer[len] = '\0'; + std::wstring ret = buffer; + delete[] buffer; + return ret; +#elif __ANDROID__ + auto len = str.length(); + wchar_t* buffer = new wchar_t[len + 1]; + mbstowcs(buffer, str.data(), len); + buffer[len] = L'\0'; + std::wstring ret = buffer; + delete[] buffer; + return ret; +#else +#error __TODO__ + return std::wstring(); +#endif // _WIN32 + } + + std::wstring M_ReplaceString(std::wstring str, std::wstring_view old_value, std::wstring_view new_value) + { + if (old_value.empty() || str.empty()) + return str; + size_t cur = 0; + while (true) + { + if (std::wstring::size_type pos; (pos = str.find(old_value, cur)) != std::wstring::npos) + { + str.replace(pos, old_value.length(), new_value); + cur = pos + 1; + } + else + break; + } + return str; + } + + _m_uint M_GetTextCount(std::wstring_view src, std::wstring_view text) + { + if (size_t pos = src.find(text); pos != std::wstring::npos) + { + int couts = 0; + couts++; + while (src.find(text, pos + 1) != std::wstring::npos) + { + pos = src.find(text, pos + 1); + couts++; + } + return couts; + } + return 0; + } + + std::wstring M_GetTextLine(std::wstring_view src, _m_uint line) + { + if (line == 0) return L""; + + size_t start = 0; + size_t end = src.find(L'\n'); + + std::wstring ret; + + size_t i = 1; + while (end != std::wstring::npos) + { + if (line == i) + return std::wstring(src.substr(start, end - start)); + + start = end + 1; + end = src.find(L'\n', start); + i++; + } + if (i == line) + return std::wstring(src.substr(start, end - start)); + + return L""; + } + +#define M_STOX(x) try { ret = x; } catch(const std::exception& ex) { MErrorThrow(ex.what()); } + + _m_int M_StoInt(std::wstring_view str) + { + if (str.empty()) return 0; + _m_int ret = 0; + M_STOX(std::stoi(str.data())) + return ret; + } + + _m_long M_StoLong(std::wstring_view str) + { + if (str.empty()) return 0l; + _m_long ret = 0; + M_STOX(std::stol(str.data())) + return ret; + } + + _m_long64 M_StoLong64(std::wstring_view str) + { + if (str.empty()) return 0ll; + _m_long64 ret = 0; + M_STOX(std::stoll(str.data())) + return ret; + } + + _m_ulong M_StoULong(std::wstring_view str) + { + if (str.empty()) return 0u; + unsigned long ret = 0; + M_STOX(std::stoul(str.data())) + return (_m_ulong)ret; + } + + _m_ulong64 M_StoULong64(std::wstring_view str) + { + if (str.empty()) return 0ull; + _m_ulong64 ret = 0; + M_STOX(std::stoull(str.data())) + return ret; + } + + float M_StoFloat(std::wstring_view str) + { + if (str.empty()) return 0.f; + float ret = 0.f; + M_STOX(std::stof(str.data())) + return ret; + } + + double M_StoDouble(std::wstring_view str) + { + if (str.empty()) return 0.0; + double ret = 0.0; + M_STOX(std::stod(str.data())) + return ret; + } + + void M_GetAttribValue(std::wstring_view value, std::vector& destValue, _m_uint count) + { + //清除空格 + std::wstring dst = M_ReplaceString(value.data(), L" ", L""); + //将分号改为换行符 + dst = M_ReplaceString(dst, L",", L"\n"); + dst += L"\n"; + //获取每行的内容 + _m_uint line = M_GetTextCount(dst, L"\n"); + for (_m_uint i = 0; i < count; i++) + { + if (i + 1 <= line) + destValue.push_back(M_GetTextLine(dst, i + 1)); + else + destValue.emplace_back(L""); + } + } + + void M_GetAttribValueInt(std::wstring_view value, std::vector& destValue, _m_uint count) + { + std::vector list; + M_GetAttribValue(value, list, count); + for (_m_uint i = 0; i < count; i++) + { + if (list.size() > i && !list[i].empty()) + destValue.push_back(M_StoInt(list[i])); + else + destValue.push_back(0); + } + } + + _m_color M_GetAttribValueColor(std::wstring_view value) + { + std::wstring dst = M_ReplaceString(value.data(), L" ", L""); + dst = M_ReplaceString(dst, L",", L"\n"); + dst += L"\n"; + //获取每行的内容 + _m_uint line = M_GetTextCount(dst, L"\n"); + int rgba[4]{ 0 }; + for (_m_uint i = 0; i < 4; i++) + { + if (i + 1 <= line) + { + std::wstring str = M_GetTextLine(dst, i + 1); + if (i == 0 && str.find(L"@hex:") == 0) + { + str = str.substr(5); + return { str, str.length() == 8 }; + } + rgba[i] = M_StoInt(str); + } + else + rgba[i] = 0xFF; + } + return Color::M_RGBA((_m_byte)rgba[0], (_m_byte)rgba[1], (_m_byte)rgba[2], (_m_byte)rgba[3]); + } + + namespace Rect + { + bool IsCross(const _m_rect& rect1, const _m_rect& rect2) + { + int x1 = rect1.left; + int y1 = rect1.top; + int x2 = rect1.left + rect1.GetWidth(); + int y2 = rect1.top + rect1.GetHeight(); + + int x3 = rect2.left; + int y3 = rect2.top; + int x4 = rect2.left + rect2.GetWidth(); + int y4 = rect2.top + rect2.GetHeight(); + + return (((x1 >= x3 && x1 < x4) || (x3 >= x1 && x3 <= x2)) && + ((y1 >= y3 && y1 < y4) || (y3 >= y1 && y3 <= y2))); + } + + bool IsPtInside(const _m_rect& rect, const UIPoint& point) + { + return (rect.left - point.x) * (rect.right - point.x) <= 0 && (rect.top - point.y) * (rect.bottom - point.y) <= 0; + } + + void Offset(_m_rect* rect, int dx, int dy) + { + rect->left += dx; + rect->right += dx; + rect->top += dy; + rect->bottom += dy; + } + + void Inflate(_m_rect* rect, int dx, int dy) + { + rect->left -= dy; + rect->right += dy; + rect->top -= dy; + rect->top += dy; + } + + void Union(_m_rect* dst, const _m_rect* rect1, const _m_rect* rect2) + { + _m_rect pRet; + + long rect1_ = rect1->left; + + bool rc1 = rect1->left >= rect1->right || rect1->top >= rect1->bottom; + bool rc2 = rect2->left >= rect2->right || rect2->top >= rect2->bottom; + if (rc1) + { + if (rc2) + { + dst->left = 0; + dst->top = 0; + dst->right = 0; + dst->bottom = 0; + return; + } + pRet = *rect2; + } + else + { + if (!rc2) + { + long num[3]; + if (rect1_ >= rect2->left) + rect1_ = rect2->left; + dst->left = rect1_; + num[0] = rect1->top; + if (num[0] >= rect2->top) + num[0] = rect2->top; + dst->top = num[0]; + num[1] = rect1->right; + if (num[1] <= rect2->right) + num[1] = rect2->right; + dst->right = num[1]; + num[2] = rect1->bottom; + if (num[2] <= rect2->bottom) + num[2] = rect2->bottom; + dst->bottom = num[2]; + return; + } + pRet = *rect1; + } + *dst = pRet; + } + + void Intersect(_m_rect* dst, const _m_rect* rect1, const _m_rect* rect2) + { + long rc1 = rect1->left; + if (rect1->left <= rect2->left) + rc1 = rect2->left; + dst->left = rc1; + + long rc2 = rect1->right; + if (rc2 >= rect2->right) + rc2 = rect2->right; + dst->right = rc2; + + if (rc1 < rc2) + { + long rc3 = rect1->top; + if (rc3 <= rect2->top) + rc3 = rect2->top; + dst->top = rc3; + + long rc4 = rect1->bottom; + if (rc4 >= rect2->bottom) + rc4 = rect2->bottom; + dst->bottom = rc4; + if (rc3 < rc4) + return; + } + dst->left = 0; + dst->top = 0; + dst->right = 0; + dst->bottom = 0; + } + } + + void M_Sleep(_m_uint ms) + { + if (ms > 1) + ms--; +#ifdef _WIN32 + timeBeginPeriod(1); +#endif // _WIN32 + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +#ifdef _WIN32 + timeEndPeriod(1); +#endif // _WIN32 + + } +} \ No newline at end of file diff --git a/MiaoUI/src/source/Mui_Settings.cpp b/MiaoUI/src/source/Mui_Settings.cpp new file mode 100644 index 0000000..a3f6c3e --- /dev/null +++ b/MiaoUI/src/source/Mui_Settings.cpp @@ -0,0 +1,78 @@ +/** + * FileName: Mui_Settings.h + * Note: MiaoUI 附加设置实现 + * + * Copyright (C) 2022-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-1-23 Create +*/ +#include +#include + +namespace Mui::Settings +{ + _m_lpcwstr MuiLogoText = LR"( +___ ___ _ _ _ _____ +| .\/. |(_) __ _ ___ | | | ||_ _| +| |\/| || | / _` | / _ \ | | | | | | +| | | || || (_| || (_) || |_| | _| |_ +\_| |_/|_| \__,_| \___/ \___/ \___/ +)"; + + //UI引擎版本号 +#ifdef _WIN32 + _m_lpcwstr MuiEngineVer = L"v2.0.8 (Lite)"; +#endif // _WIN32 + + void UIMessageLoop() + { +#ifdef _WIN32 + + MSG msg; + while (GetMessage(&msg, nullptr, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + +#endif // _WIN32 + } + + std::unordered_map g_exflagList; + std::mutex g_exflagLock; + void SetExtensionFlag(ExSection section, _m_param flag, bool override) + { + std::lock_guard lock{ g_exflagLock }; + g_exflagList[section] = { flag, override }; + } + + void ResExtensionFlag(ExSection section) + { + std::lock_guard lock{ g_exflagLock }; + if (auto iter = g_exflagList.find(section); iter != g_exflagList.end()) + g_exflagList.erase(iter); + } + + bool GetExtensionFlag(ExSection section, std::pair<_m_param, bool>& param) + { + std::lock_guard lock{ g_exflagLock }; + if (const auto iter = g_exflagList.find(section); iter != g_exflagList.end()) + { + param = iter->second; + return true; + } + return false; + } +}; \ No newline at end of file diff --git a/MiaoUI/src/source/Mui_XML.cpp b/MiaoUI/src/source/Mui_XML.cpp new file mode 100644 index 0000000..72c0438 --- /dev/null +++ b/MiaoUI/src/source/Mui_XML.cpp @@ -0,0 +1,704 @@ +/** + * FileName: Mui_XML.cpp + * Note: 界面创建助手实现 + * + * Copyright (C) 2020-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-12-12 Create +*/ +#include +#include +#include + +namespace Mui::XML +{ + using namespace Ctrl; + + bool MuiXML::SetDefPropGroupAttrib(std::wstring_view name, + const std::vector>& attribList, bool draw) + { + //查找属性组 + PropGroup* propPtr = nullptr; + for(auto& prop : m_defpropList) + { + if (prop.id != name) + continue; + propPtr = ∝ + break; + } + if(!propPtr) + return false; + + //设置所有指定控件属性 + for (auto& ctrl : m_bindCtrlList) + { + if (ctrl.first->GetClsName() != name) + continue; + + //遍历要添加的属性 + for (const auto& [attribName, attribValue] : attribList) + { + propPtr->prop[attribName] = attribValue; + + //设置属性 + SetAttribute(ctrl.first, attribName, attribValue); + } + } + if (draw) + m_window->UpdateDisplay(nullptr); + return true; + } + + bool MuiXML::AddStringList(std::wstring_view name, std::wstring_view value) + { + return m_stringList.insert(std::make_pair(name, value)).second; + } + + bool MuiXML::SetStringValue(std::wstring_view name, std::wstring_view value, bool draw) + { + auto iter = m_stringList.find(name.data()); + if (iter == m_stringList.end()) + return false; + iter->second = value.data(); + + //更新关联的控件 + for(auto& ctrl : m_bindCtrlList) + { + for(auto& str : ctrl.second.strList) + { + if (str.str.first != name) + continue; + + if(str.menuIndex == -1) + SetAttributeInternal(ctrl.first, str.str.second, value.data(), true); + } + } + if(draw) + m_window->UpdateDisplay(nullptr); + return true; + } + + bool MuiXML::DeleteStringList(std::wstring_view name) + { + if (auto iter = m_stringList.find(name.data()); iter != m_stringList.end()) + { + m_stringList.erase(iter); + //清除关联控件绑定 + for(auto& ctrl : m_bindCtrlList) + { + for (auto _it = ctrl.second.strList.begin(); _it != ctrl.second.strList.end();) + { + if (_it->str.first != name) + { + ++_it; + continue; + } + _it = ctrl.second.strList.erase(_it); + } + } + return true; + } + return false; + } + + std::wstring MuiXML::GetStringValue(std::wstring_view name) + { + if (auto iter = m_stringList.find(name.data()); iter != m_stringList.end()) + { + return iter->second; + } + return name.data(); + } + + MuiXML::MuiXML(Window::UIWindowBasic* window) + { + m_window = window; + m_window->m_renderRoot->SetUnBindCallback([this](MRenderNode* node) + { + if(auto iter = m_bindCtrlList.find((UIControl*)node);iter != m_bindCtrlList.end()) + { + m_bindCtrlList.erase(iter); + } + }); + } + + MuiXML::~MuiXML() + { + m_window->m_renderRoot->SetUnBindCallback(nullptr); + } + + bool MuiXML::CreateUIFromXML(UIControl* parents, std::wstring xmlDoc) + { + M_ASSERT(parents) + pugi::xml_document doc; + + //把非标准的\\n替换成XML标准中的换行符代码 + xmlDoc = Helper::M_ReplaceString(xmlDoc, L"\\n", L" "); + xmlDoc = L"" + xmlDoc + L""; + + if (!doc.load_string(xmlDoc.c_str())) + return false; + //遍历XML + std::function Enum = [&](pugi::xml_node node, UIControl* parent) + { + //子节点 + for (auto& nodes : node.children()) + { + std::wstring TagName = nodes.name(); + UIControl* control = CreateControl(TagName, parent); + if (!control) control = parents; + + //属性组 + bool propGroup = TagName == L"PropGroup"; + PropGroup prop; + + if(!propGroup) + { + //应用默认属性组 + for(auto& attrib : m_defpropList) + { + if (attrib.id != TagName) + continue; + for (auto& value : attrib.prop) + SetAttribute(control, value.first, value.second); + } + } + + //遍历属性 + for (auto& attrib : nodes.attributes()) + { + if (propGroup) + { + std::wstring name = attrib.name(); + if (name == L"id") + prop.id = attrib.value(); + else + prop.prop.insert({ name, attrib.value() }); + } + else + SetAttribute(control, attrib.name(), attrib.value()); + } + if (propGroup) + m_propList.push_back(prop); + //遍历子节点 + Enum(nodes, control); + } + }; + if (auto root = doc.child(L"root")) + { + Enum(root, parents); + return true; + } + return false; + } + + bool MuiXML::AddFontStyle(std::wstring_view name, const UILabel::Attribute& style) + { + if (FindFontStyle(name) != &defaultStyle) + return false; + m_fontStyleList.insert(std::make_pair(name, style)); + return true; + } + +#if MUI_MXML_ENABLE_DEFSTYLE + + void MuiXML::LoadDefaultStyle() + { + if (m_defStyleInited) return; + m_defStyleInited = true; + + DefStyle::_g_mui_default_uistyle_loadxml(Mgr()); + AddDefPropGroup(DefStyle::_g_mui_default_uistyle, true); + } + +#endif + + UILabel::Attribute MuiXML::GetFontStyle(std::wstring_view name) + { + return *FindFontStyle(name); + } + + bool MuiXML::DeleteFontStyle(std::wstring_view name) + { + auto iter = m_fontStyleList.find(name.data()); + if (iter != m_fontStyleList.end()) + { + m_fontStyleList.erase(iter); + return true; + } + return false; + } + + bool MuiXML::CreatePropGroup(std::wstring_view id) + { + for (auto& i : m_propList) + { + if (i.id != id) continue; + return false; + } + PropGroup group; + group.id = id; + m_propList.push_back(group); + return true; + } + + bool MuiXML::DeletePropGroup(std::wstring_view id) + { + for (size_t i = 0; i < m_propList.size(); ++i) + { + if (m_propList[i].id != id) continue; + m_propList.erase(m_propList.begin() + (long long)i); + return true; + } + return false; + } + + bool MuiXML::DeletePropGroupAttrib(std::wstring_view id, std::wstring_view attribName) + { + std::unordered_map* propList = nullptr; + for (auto& i : m_propList) + { + if (i.id != id) continue; + propList = &i.prop; + } + if (!propList) return false; + + auto iter = propList->find(attribName.data()); + if(iter == propList->end()) + return false; + + propList->erase(iter); + return true; + } + + bool MuiXML::SetPropGroupAttrib(std::wstring_view id, std::wstring_view attribName, std::wstring_view attribValue) + { + std::unordered_map* propList = nullptr; + for (auto& i : m_propList) + { + if (i.id != id) continue; + propList = &i.prop; + } + if (!propList) return false; + + auto iter = propList->find(attribName.data()); + if (iter == propList->end()) + return false; + + iter->second = attribValue; + + return true; + } + + bool MuiXML::AddPropGroupAttrib(std::wstring_view id, std::wstring_view attribName, std::wstring_view attribValue) + { + std::unordered_map* propList = nullptr; + for (auto& i : m_propList) + { + if (i.id != id) continue; + propList = &i.prop; + } + if (!propList) return false; + + return propList->insert({ attribName.data(), attribValue.data()}).second; + } + + bool MuiXML::ApplyPropGroup(UIControl* dst, std::wstring_view id, bool draw) + { + if (!dst) + return false; + + for (auto& prop : m_propList) + { + if (prop.id != id) + continue; + for (auto& p : prop.prop) + { + if (p.first != L"prop") + SetAttribute(dst, p.first, p.second); + } + if (draw) + dst->UpdateDisplay(); + return true; + } + return false; + } + + bool MuiXML::AddDefPropGroup(std::wstring_view xml, bool replace) + { + if (xml.empty()) return false; + + std::wstring _xml = L""; + _xml += xml; + _xml += +L""; + + pugi::xml_document doc; + + if (!doc.load_string(_xml.c_str())) + return false; + + auto FindProp = [&](std::wstring_view id) -> PropGroup* + { + for(auto& prop : m_defpropList) + { + if (prop.id == id) return ∝ + } + return nullptr; + }; + + for(auto& list : doc.child(L"root").children()) + { + if (std::wstring_view(list.name()) != L"DefPropGroup") + continue; + PropGroup prop; + for (auto& attrib : list.attributes()) + { + std::wstring name = attrib.name(); + if (name == L"control") + prop.id = attrib.value(); + else + prop.prop[name] = attrib.value(); + } + if (prop.id.empty()) + continue; + + const auto defprop = FindProp(prop.id); + if (!defprop) + { + m_defpropList.push_back(prop); + continue; + } + //检查和替换属性 + for(const auto& [name, value] : prop.prop) + { + if (replace) + defprop->prop[name] = value; + else + defprop->prop.insert({ name, value }); + } + } + + return true; + } + + bool MuiXML::DeleteDefPropGroup(std::wstring_view name) + { + for (size_t i = 0; i < m_defpropList.size(); i++) + { + if (m_defpropList[i].id == name) + { + m_defpropList.erase(m_defpropList.begin() + (long long)i); + return true; + } + } + return false; + } + + UIControl* MuiXML::CreateControl(std::wstring_view controlName, UIControl* parent) + { + if (controlName == L"PropGroup") + return nullptr; + + UIControl* ret = CtrlMgr::CreateControl(controlName, parent); + if (controlName != L"root" && !ret) + { +#if MUI_MXML_THROW_UNKNOWCTRL + if (controlName != L"UIControl") + { + std::wstring err = L"Unknown control: "; + err += controlName; + MErrorThrow(err); + } +#endif + ret = new UIControl(); + parent->AddChildren(ret); + } + if (ret) + { + m_bindCtrlList.insert(std::make_pair(ret, bindInfo())); + } + return ret; + } + + void MuiXML::SetAttribute(UIControl* control, std::wstring_view name, std::wstring value) + { + SetAttributeInternal(control, name, std::move(value), false); + } + + void MuiXML::SetAttributeInternal(UIControl* control, std::wstring_view name, std::wstring value, + bool strlist) + { + //映射字符串 + bool strlistAttrib = false; + if (value.length() > 1) + { + //使用字符串表 + if (value[0] == '#') + { + strlistAttrib = true; + + auto strName = value.substr(1, value.length() - 1); + auto iter = m_stringList.find(strName); + if (iter == m_stringList.end()) + goto next; + + value = iter->second; + + //设置绑定列表属性 + auto ctrl = m_bindCtrlList.find(control); + if (ctrl == m_bindCtrlList.end()) + goto next; + + //查看是否已经绑定了当前属性名 + bool finded = false; + for (auto& prop : ctrl->second.strList) + { + if (prop.str.second == name) + { + prop.str.first = strName; + finded = true; + break; + } + } + //没找到 添加 + if (!finded) + { + bindInfo::strdata data; + data.str = std::make_pair(strName, name.data()); + data.menuIndex = -1; + ctrl->second.strList.push_back(data); + } + } + //转义成# + else if (value[0] == '\\' && value[1] == '#') + { + value = Helper::M_ReplaceString(value, L"\\#", L"#"); + } + } + if (!strlistAttrib && ! strlist) + { + auto ctrl = m_bindCtrlList.find(control); + if (ctrl == m_bindCtrlList.end()) + goto next; + + //查看是否已经绑定了当前属性名 如果有 则覆盖默认属性 + for (size_t i = 0; i < ctrl->second.strList.size(); ++i) + { + auto& item = ctrl->second.strList[i]; + if (item.str.second != name) + continue; + + ctrl->second.strList.erase(ctrl->second.strList.begin() + i); + break; + } + } + next: + + //使用属性组 + if (name == L"prop") + { + for (auto& prop : m_propList) + { + if (prop.id != value) + continue; + for (auto& p : prop.prop) + { + if (p.first != L"prop") + SetAttribute(control, p.first, p.second); + } + break; + } + } + else if (name == L"align") + { + //映射到枚举值 + if (value == L"Block") + value = std::to_wstring(UIAlignment_Block); + else if (value == L"LinearV") + value = std::to_wstring(UIAlignment_LinearV); + else if (value == L"LinearVB") + value = std::to_wstring(UIAlignment_LinearVB); + else if (value == L"LinearVR") + value = std::to_wstring(UIAlignment_LinearVR); + else if (value == L"LinearVBR") + value = std::to_wstring(UIAlignment_LinearVBR); + else if (value == L"LinearH") + value = std::to_wstring(UIAlignment_LinearH); + else if (value == L"LinearHL") + value = std::to_wstring(UIAlignment_LinearHL); + else if (value == L"LinearHB") + value = std::to_wstring(UIAlignment_LinearHB); + else if (value == L"LinearHLB") + value = std::to_wstring(UIAlignment_LinearHLB); + else if (value == L"Absolute") + value = std::to_wstring(UIAlignment_Absolute); + else if (value == L"Center") + value = std::to_wstring(UIAlignment_Center); + else if (value == L"Grid") + value = std::to_wstring(UIAlignment_Grid); + } + else if (name == L"frame") + { + std::vector dst; + Helper::M_GetAttribValue(value, dst, 4); + + UINodeBase::PosSizeUnit posUint; + UINodeBase::PosSizeUnit sizeUint = posUint; + + for (size_t i = 0; i < dst.size(); i++) + { + const auto& v = dst[i]; + auto type = UINodeBase::Percentage; + if (v.find('%') == std::wstring::npos) + { + if (v.find('f') == std::wstring::npos) + continue; + type = UINodeBase::FillMinus; + } + + if (i < 2) + { + if (i == 0) + posUint.x_w = type; + else + posUint.y_h = type; + } + else + { + if (i == 2) + sizeUint.x_w = type; + else + sizeUint.y_h = type; + } + } + + control->SetPosUnit(posUint, false); + control->SetSizeUnit(sizeUint, false); + } + else if (name == L"size") + { + std::vector dst; + Helper::M_GetAttribValue(value, dst, 2); + UINodeBase::PosSizeUnit sizeUint; + + for (size_t i = 0; i < 2; ++i) + { + auto type = UINodeBase::Percentage; + if (dst[i].find('%') == std::wstring::npos) + { + if (dst[i].find('f') == std::wstring::npos) + continue; + type = UINodeBase::FillMinus; + } + + if (i == 0) + sizeUint.x_w = type; + else + sizeUint.y_h = type; + } + control->SetSizeUnit(sizeUint, false); + } + else if (name == L"pos") + { + std::vector dst; + Helper::M_GetAttribValue(value, dst, 2); + + UINodeBase::PosSizeUnit posUint; + + for (size_t i = 0; i < 2; ++i) + { + auto type = UINodeBase::Percentage; + if (dst[i].find('%') == std::wstring::npos) + { + if (dst[i].find('f') == std::wstring::npos) + continue; + type = UINodeBase::FillMinus; + } + if (i == 0) + posUint.x_w = type; + else + posUint.y_h = type; + } + control->SetPosUnit(posUint, false); + } + //内置字体样式属性 + else if (auto type = CtrlMgr::GetAttributeType(control->GetClsName(), name); type != CtrlMgr::AttribType::defaults) + { + switch (type) + { + case CtrlMgr::AttribType::labelStyle: + value = std::to_wstring(_m_ptrv(FindFontStyle(value))); + break; + case CtrlMgr::AttribType::listfontStyle: + { + auto p = FindFontStyle(value); + UIListBox::ItemFont ifont; + ifont.font = p->font; + ifont.fontColor = p->fontColor; + ifont.fontCustom = p->fontCustom; + ifont.fontSize = p->fontSize; + ifont.fontStyle = p->fontStyle; + ifont.textAlign = p->textAlign; + control->SetAttribute(name, std::to_wstring(_m_ptrv(&ifont)), false); + return; + } + case CtrlMgr::AttribType::UIResource: + { + UIResource res = m_window->GetResourceMgr()->ReadResource(value); + control->SetAttribute(name, std::to_wstring((_m_ptrv)&res), false); + res.Release(); + return; + } + case CtrlMgr::AttribType::UIStyle: + { + UIStyle* style = m_window->GetResourceMgr()->FindStyle(value); + if (style || value.empty()) + value = std::to_wstring(_m_ptrv(style)); + else + { + MErrorThrow(L"Unknown Style: " + value); + return; + } + } + break; + case CtrlMgr::AttribType::UIBitmap: + { + auto bitmap = m_window->GetResourceMgr()->CreateSharedUIBitmap(value); + + if (bitmap || value.empty()) + { + value = std::to_wstring(_m_ptrv(bitmap.get())); + control->SetAttribute(name, value, false); + return; + } + + MErrorThrow(L"Unknown Bitmap: " + value); + return; + } + default: + break; + } + } + control->SetAttribute(name, value, false); + } + + UILabel::Attribute* MuiXML::FindFontStyle(std::wstring_view name) + { + auto iter = m_fontStyleList.find(name.data()); + if (iter != m_fontStyleList.end()) + return &iter->second; + return &defaultStyle; + } +} \ No newline at end of file diff --git a/MiaoUI/src/source/Render/Graphs/Mui_GdipBaseObj.cpp b/MiaoUI/src/source/Render/Graphs/Mui_GdipBaseObj.cpp new file mode 100644 index 0000000..71ae257 --- /dev/null +++ b/MiaoUI/src/source/Render/Graphs/Mui_GdipBaseObj.cpp @@ -0,0 +1,348 @@ +/** + * FileName: Mui_GdipBaseObj.cpp + * Note: GdiPlus 基本对象实现 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-11-6 Create +*/ + +#include + +namespace Mui::Render +{ + using namespace Color; + using namespace Gdiplus; + + void MBitmap_GDIP::ReleaseThis() + { + if(m_bitmap) + DeleteObject(m_bitmap); + if(m_hdc) + DeleteDC(m_hdc); + } + + _m_rect MCanvas_GDIP::GetSubRect() + { + return { 0, 0, m_size.width, m_size.height }; + } + + void MCanvas_GDIP::ReleaseThis() + { + if(m_bitmap) + DeleteObject(m_bitmap); + if (m_hdc) + DeleteDC(m_hdc); + } + + void MPen_GDIP::SetColor(_m_color color) + { + m_colorSrc = color; + SetOpacity(m_alpha); + } + + void MPen_GDIP::SetWidth(_m_uint width) + { + m_pen->SetWidth((Gdiplus::REAL)width); + } + + void MPen_GDIP::SetOpacity(_m_byte alpha) + { + m_alpha = alpha; + _m_byte tmp = M_GetAValue(m_colorSrc); + m_pen->SetColor(Gdiplus::Color(_m_byte((float)alpha * (float)tmp / 255.f), + M_GetRValue(m_colorSrc), M_GetGValue(m_colorSrc), M_GetBValue(m_colorSrc))); + } + + void MPen_GDIP::SetWidthAndColor(_m_uint width, _m_color color) + { + SetWidth(width); + SetColor(color); + } + + _m_color MPen_GDIP::GetColor() + { + return m_colorSrc; + } + + _m_uint MPen_GDIP::GetWidth() + { + return (int)m_pen->GetWidth(); + } + + _m_byte MPen_GDIP::GetOpacity() + { + return m_alpha; + } + + void MPen_GDIP::ReleaseThis() + { + delete m_pen; + } + + void MBrush_GDIP::SetColor(_m_color color) + { + m_colorSrc = color; + SetOpacity(m_alpha); + } + + void MBrush_GDIP::SetOpacity(_m_byte alpha) + { + m_alpha = alpha; + _m_byte tmp = M_GetAValue(m_colorSrc); + m_brush->SetColor(Gdiplus::Color(_m_byte((float)alpha * (float)tmp / 255.f), + M_GetRValue(m_colorSrc), M_GetGValue(m_colorSrc), M_GetBValue(m_colorSrc))); + } + + _m_color MBrush_GDIP::GetColor() + { + return m_colorSrc; + } + + _m_byte MBrush_GDIP::GetOpacity() + { + return m_alpha; + } + + void MBrush_GDIP::ReleaseThis() + { + delete m_brush; + } + + _m_uint MGradientBrush_GDIP::GetColorPosCount() + { + return (_m_uint)m_vertex.size(); + } + + _m_color MGradientBrush_GDIP::GetPosColor(_m_uint index) + { + return m_vertex[index].first; + } + + void MGradientBrush_GDIP::SetOpacity(_m_byte alpha) + { + m_alpha = alpha; + auto color = new Gdiplus::Color[m_vertex.size()]; + auto pt = new Gdiplus::REAL[m_vertex.size()]; + for (size_t i = 0; i < m_vertex.size(); ++i) + { + _m_byte tmp = M_GetAValue(m_vertex[i].first); + color[i] = Gdiplus::Color(_m_byte((float)alpha * (float)tmp / 255.f), + M_GetRValue(m_vertex[i].first), M_GetGValue(m_vertex[i].first), M_GetBValue(m_vertex[i].first)); + pt[i] = m_vertex[i].second; + } + if(m_vertex.size() > 2) + m_brush->SetInterpolationColors(color + 2, pt, (int)m_vertex.size() - 2); + else + m_brush->SetLinearColors(color[0], color[1]); + delete[] color; + delete[] pt; + } + + _m_byte MGradientBrush_GDIP::GetOpacity() + { + return m_alpha; + } + + UIPoint MGradientBrush_GDIP::GetStartPoint() + { + return m_start; + } + + void MGradientBrush_GDIP::SetStartPoint(UIPoint start) + { + Gdiplus::PointF pt = { (Gdiplus::REAL)start.x, (Gdiplus::REAL)start.y }; + m_start = start; + delete m_brush; + m_brush = new Gdiplus::LinearGradientBrush(pt, { (float)m_end.x, (float)m_end.y }, + Gdiplus::Color(0, 0, 0, 0), Gdiplus::Color(0, 0, 0, 0)); + SetOpacity(m_alpha); + } + + UIPoint MGradientBrush_GDIP::GetEndPoint() + { + return m_end; + } + + void MGradientBrush_GDIP::SetEndPoint(UIPoint end) + { + Gdiplus::PointF pt = { (Gdiplus::REAL)end.x, (Gdiplus::REAL)end.y }; + m_end = end; + delete m_brush; + m_brush = new Gdiplus::LinearGradientBrush({ (float)m_start.x, (float)m_start.y }, pt, + Gdiplus::Color(0, 0, 0, 0), Gdiplus::Color(0, 0, 0, 0)); + SetOpacity(m_alpha); + } + + void MGradientBrush_GDIP::ReleaseThis() + { + delete m_brush; + } + + void MFont_GDIP::SetFontName(std::wstring_view name) + { + m_font = name; + UpdateFont(); + } + + void MFont_GDIP::SetFontSize(_m_uint size, std::pair<_m_uint, _m_uint> range) + { + m_fontsize = size; + UpdateFont(); + } + + void MFont_GDIP::SetFontStyle(UIFontStyle style, std::pair<_m_uint, _m_uint> range) + { + m_style = style; + UpdateFont(); + } + + void MFont_GDIP::SetFontColor(MBrush* brush, std::pair<_m_uint, _m_uint> range) + { + Gdiplus::Color cr; + static_cast(brush)->m_brush->GetColor(&cr); + m_color = cr.GetValue(); + } + + void MFont_GDIP::SetText(std::wstring_view text) + { + m_text = text; + CalcMetrics(); + } + + UIRect MFont_GDIP::GetMetrics() + { + return m_metrics; + } + + const UIString& MFont_GDIP::GetFontName() + { + return m_font; + } + + _m_uint MFont_GDIP::GetFontSize() + { + return m_fontsize; + } + + UIFontStyle MFont_GDIP::GetFontStyle() + { + return m_style; + } + + _m_color MFont_GDIP::GetFontColor() + { + return m_color; + } + + const UIString& MFont_GDIP::GetText() + { + return m_text; + } + + void MFont_GDIP::ReleaseThis() + { + delete m_fontObj; + } + + void MFont_GDIP::CalcMetrics() + { + GraphicsPath graphicsPathObj; + FontFamily fontfamily; + m_fontObj->GetFamily(&fontfamily); + StringFormat f; + f.SetAlignment(StringAlignmentNear); + f.SetLineAlignment(StringAlignmentNear); + graphicsPathObj.AddString(m_text.cstr(), -1, &fontfamily, m_fontObj->GetStyle(), m_fontObj->GetSize(),Point(1,1), &f); + + RectF rcBound; + graphicsPathObj.GetBounds(&rcBound); + + m_metrics = { 0, 0, (int)round(rcBound.X + rcBound.Width), + (int)round(rcBound.Y + rcBound.Height) }; + } + + void MFont_GDIP::UpdateFont() + { + Gdiplus::FontFamily family(m_font.cstr()); + + int fontStyle = FontStyleRegular; + if (m_style.bold) fontStyle |= FontStyleBold; + if (m_style.italics) fontStyle |= FontStyleItalic; + if (m_style.underline) fontStyle |= FontStyleUnderline; + if (m_style.strikeout) fontStyle |= FontStyleStrikeout; + + delete m_fontObj; + m_fontObj = new Gdiplus::Font(&family, (REAL)m_fontsize, fontStyle, UnitPixel); + CalcMetrics(); + } + + MGeometry::MGeometryTypes MGeometry_GDIP::GetGeometryType() + { + return m_type; + } + + void MGeometry_GDIP::ReleaseThis() + { + delete m_geometry; + } + + MRgn_GDIP::MRgn_GDIP(int width, int height) + { + width = (width + 3) / 4 * 4; + height = (height + 3) / 4 * 4; + + m_hdc = CreateCompatibleDC(nullptr); + + BITMAPINFO bmi = {}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = width; + bmi.bmiHeader.biHeight = height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 8; + bmi.bmiHeader.biCompression = BI_RGB; + bmi.bmiHeader.biSizeImage = width * height; + + m_bitmap = CreateDIBSection(m_hdc, &bmi, DIB_RGB_COLORS, &m_bits, nullptr, 0); + + SelectObject(m_hdc, m_bitmap); + + m_size = { width, height }; + } + + void MRgn_GDIP::Set(HRGN hRgn) + { + RECT rect{ 0,0, m_size.width, m_size.height }; + FillRect(m_hdc, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH)); + SelectClipRgn(m_hdc, hRgn); + FillRect(m_hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH)); + SelectClipRgn(m_hdc, nullptr); + } + + void MRgn_GDIP::ReleaseProc() + { + if(m_bitmap) + DeleteObject(m_bitmap); + if(m_hdc) + DeleteDC(m_hdc); + } + + _m_byte MRgn_GDIP::GetBit(int x, int y) + { + if (x < 0 || x >= m_size.width || y < 0 || y >= m_size.height) + return 0; + return *((_m_lpbyte)m_bits + y * m_size.width + x); + } +} diff --git a/MiaoUI/src/source/Render/Graphs/Mui_GdipRender.cpp b/MiaoUI/src/source/Render/Graphs/Mui_GdipRender.cpp new file mode 100644 index 0000000..980c0c2 --- /dev/null +++ b/MiaoUI/src/source/Render/Graphs/Mui_GdipRender.cpp @@ -0,0 +1,706 @@ +/** + * FileName: Mui_GdipRender.cpp + * Note: GdiPlus 渲染器 + * + * Copyright (C) 2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2024-11-6 Create +*/ + +#include +#include +#pragma comment(lib, "shlwapi.lib") + +namespace Mui::Render +{ + using namespace Gdiplus; + + BYTE GdiAlpha::s_byAlphaBack[65536]; + + const CLSID* GetEncoderClsid(MImgFormat format) + { + static std::map encoderClsidMap; + + if (encoderClsidMap.empty()) + { + UINT numEncoders = 0, size = 0; + GetImageEncodersSize(&numEncoders, &size); + + if (size == 0) return nullptr; // No encoders found + + std::unique_ptr buffer(new BYTE[size]); + ImageCodecInfo* encoders = reinterpret_cast(buffer.get()); + + GetImageEncoders(numEncoders, size, encoders); + + for (UINT i = 0; i < numEncoders; ++i) + { + std::wstring mimeType(encoders[i].MimeType); + if (mimeType == L"image/png") encoderClsidMap[MImgFormat::PNG] = encoders[i].Clsid; + else if (mimeType == L"image/jpeg") encoderClsidMap[MImgFormat::JPG] = encoders[i].Clsid; + else if (mimeType == L"image/bmp") encoderClsidMap[MImgFormat::BMP] = encoders[i].Clsid; + } + } + + auto it = encoderClsidMap.find(format); + return it != encoderClsidMap.end() ? &it->second : nullptr; + } + + _m_lpcwstr MRender_GDIP::GetRenderName() + { + return L"GDIPlus"; + } + + bool MRender_GDIP::InitRender(_m_uint width, _m_uint height) + { + if (m_Graphics) + return true; + Resize(width, height); + return true; + } + + bool MRender_GDIP::Resize(_m_uint width, _m_uint height) + { + m_CanvasDef = (MCanvas_GDIP*)CreateCanvas(width, height, 0); + m_CanvasTmp = (MCanvas_GDIP*)CreateCanvas(width, height, 0); + m_Canvas = m_CanvasDef; + m_Rgn = new MRgn_GDIP((int)width, (int)height); + ResetCanvas(); + return true; + } + + MCanvas* MRender_GDIP::CreateCanvas(_m_uint width, _m_uint height, _m_param param) + { + Bitmap bitmap((int)width, (int)height, PixelFormat32bppARGB); + HBITMAP hbitmap = nullptr; + if(bitmap.GetHBITMAP(Gdiplus::Color::Transparent, &hbitmap) != Gdiplus::Ok) + return nullptr; + + auto ret = new MCanvas_GDIP(); + ret->m_base = m_base; + ret->m_size = { (int)width, (int)height }; + ret->m_bitmap = hbitmap; + ret->m_hdc = CreateCompatibleDC(nullptr); + SelectObject(ret->m_hdc, hbitmap); + return ret; + } + + MBitmap* MRender_GDIP::CreateBitmap(std::wstring_view path, _m_param param) + { + auto bitmap = Bitmap::FromFile(path.data()); + auto clean = RAII::scope_exit([&] { delete bitmap; }); + HBITMAP hbitmap = nullptr; + if (!bitmap || bitmap->GetHBITMAP(Gdiplus::Color::Transparent, &hbitmap) != Gdiplus::Ok) + return nullptr; + + auto ret = new MBitmap_GDIP(); + ret->m_base = m_base; + ret->m_bitmap = hbitmap; + ret->m_hdc = CreateCompatibleDC(nullptr); + ret->m_size = { (int)bitmap->GetWidth(), (int)bitmap->GetHeight() }; + SelectObject(ret->m_hdc, hbitmap); + return ret; + } + + MBitmap* MRender_GDIP::CreateBitmap(UIResource resource, _m_param param) + { + RAII::Mui_Ptr stream = SHCreateMemStream(resource.data, (UINT)resource.size); + if (!stream) + return nullptr; + + auto bitmap = Bitmap::FromStream(stream.get()); + auto clean = RAII::scope_exit([&] { delete bitmap; }); + stream = nullptr; + HBITMAP hbitmap = nullptr; + if (!bitmap || bitmap->GetHBITMAP(Gdiplus::Color::Transparent, &hbitmap) != Gdiplus::Ok) + return nullptr; + + auto ret = new MBitmap_GDIP(); + ret->m_base = m_base; + ret->m_bitmap = hbitmap; + ret->m_hdc = CreateCompatibleDC(nullptr); + ret->m_size = { (int)bitmap->GetWidth(), (int)bitmap->GetHeight() }; + SelectObject(ret->m_hdc, hbitmap); + return ret; + } + + MBitmap* MRender_GDIP::CreateBitmap(_m_uint width, _m_uint height, void* bit, _m_uint len, _m_uint stride) + { + Bitmap bitmap((int)width, (int)height, (int)stride, PixelFormat32bppARGB, (BYTE*)bit); + HBITMAP hbitmap = nullptr; + if (bitmap.GetHBITMAP(Gdiplus::Color::Transparent, &hbitmap) != Gdiplus::Ok) + return nullptr; + + auto ret = new MBitmap_GDIP(); + ret->m_base = m_base; + ret->m_bitmap = hbitmap; + ret->m_hdc = CreateCompatibleDC(nullptr); + ret->m_size = { (int)width, (int)height }; + SelectObject(ret->m_hdc, hbitmap); + return ret; + } + + MBitmap* MRender_GDIP::CreateSVGBitmap(std::wstring_view path, _m_uint width, _m_uint height, bool repColor, + _m_color color) + { + return nullptr; + } + + MBitmap* MRender_GDIP::CreateSVGBitmapFromXML(std::wstring_view xml, _m_uint width, _m_uint height, bool repColor, + _m_color color) + { + return nullptr; + } + + MPen* MRender_GDIP::CreatePen(_m_uint width, _m_color color) + { + auto ret = new MPen_GDIP(); + ret->m_base = m_base; + ret->m_pen = new Pen(Gdiplus::Color()); + ret->SetWidthAndColor(width, color); + return ret; + } + + MBrush* MRender_GDIP::CreateBrush(_m_color color) + { + auto ret = new MBrush_GDIP(); + ret->m_base = m_base; + ret->m_brush = new SolidBrush(Gdiplus::Color()); + ret->SetColor(color); + return ret; + } + + MGradientBrush* MRender_GDIP::CreateGradientBrush(const std::pair<_m_color, float>* vertex, _m_ushort count, + UIPoint start, UIPoint end) + { + if (count < 2) + return nullptr; + auto ret = new MGradientBrush_GDIP(); + ret->m_base = m_base; + ret->m_brush = new LinearGradientBrush(Point(start.x, start.y), Point(end.x, end.y), + Gdiplus::Color::Transparent, Gdiplus::Color::Transparent); + ret->m_vertex.resize(count); + ret->m_start = start; + ret->m_end = end; + for (_m_ushort i = 0; i < count; ++i) + { + ret->m_vertex[i] = vertex[i]; + } + ret->SetOpacity(255); + return ret; + } + + MFont* MRender_GDIP::CreateFonts(std::wstring_view text, std::wstring_view fontName, _m_uint fontSize, + _m_ptrv fontCollection) + { + auto ret = new MFont_GDIP(); + ret->m_base = m_base; + ret->m_font = fontName; + ret->m_text = text; + ret->m_fontsize = fontSize; + ret->UpdateFont(); + return ret; + } + + MEffects* MRender_GDIP::CreateEffects(MEffects::Types effect, float value) + { + return nullptr; + } + + MGeometry* MRender_GDIP::CreateRoundGeometry(_m_rect dest, float round) + { + round *= 2; + auto ret = new MGeometry_GDIP(); + ret->m_base = m_base; + ret->m_geometry = new GraphicsPath(); + ret->m_geometry->AddArc((float)dest.left, (float)dest.top, round, round, 180, 90); + ret->m_geometry->AddArc((float)dest.right - round, (float)dest.top, round, round, 270, 90); + ret->m_geometry->AddArc((float)dest.right - round, (float)dest.bottom - round, round, round, 0, 90); + ret->m_geometry->AddArc((float)dest.left, (float)dest.bottom - round, round, round, 90, 90); + ret->m_geometry->CloseFigure(); + return ret; + } + + MGeometry* MRender_GDIP::CreateEllipseGeometry(_m_rect dest) + { + auto ret = new MGeometry_GDIP(); + ret->m_base = m_base; + ret->m_geometry = new GraphicsPath(); + ret->m_geometry->AddEllipse((float)dest.left, (float)dest.top, (float)dest.GetWidth(), (float)dest.GetHeight()); + return ret; + } + + MCanvas* MRender_GDIP::CreateSubAtlasCanvas(_m_uint width, _m_uint height) + { + return nullptr; + } + + MBatchBitmap* MRender_GDIP::CreateBatchBitmap() + { + return nullptr; + } + + bool MRender_GDIP::CopyBitmapContent(MBitmap* dst, MBitmap* src, UIPoint dstPt, _m_rect srcRect) + { + if (!dst || !src) return false; + + return BitBlt(static_cast(dst)->m_hdc, dstPt.x, dstPt.y, srcRect.GetWidth(), srcRect.GetHeight(), + static_cast(src)->m_hdc, srcRect.left, srcRect.top, SRCCOPY); + } + + bool MRender_GDIP::CopyBitmapContent(MCanvas* dst, MCanvas* src, UIPoint dstPt, _m_rect srcRect) + { + if (!dst || !src) return false; + + bool ret = BitBlt(static_cast(dst)->m_hdc, dstPt.x, dstPt.y, srcRect.GetWidth(), srcRect.GetHeight(), + static_cast(src)->m_hdc, srcRect.left, srcRect.top, SRCCOPY); + return ret; + } + + void MRender_GDIP::BeginDraw() + { + } + + void MRender_GDIP::SetCanvas(MCanvas* canvas) + { + m_Canvas = (MCanvas_GDIP*)canvas; + m_Graphics = std::make_unique(m_Canvas->m_hdc); + } + + void MRender_GDIP::ResetCanvas() + { + SetCanvas(m_CanvasDef.get()); + } + + void MRender_GDIP::DrawBitmap(MBitmap* img, _m_byte alpha, _m_rect dest, _m_rect src, bool highQuality) + { + if (dest.IsEmpty()) + dest = { 0, 0, int(img->GetWidth()), int(img->GetHeight()) }; + + if (src.IsEmpty()) + src = { 0, 0, int(img->GetWidth()), int(img->GetHeight()) }; + + drawBitmap(static_cast(img)->m_hdc, alpha, dest, src, highQuality); + } + + void MRender_GDIP::DrawBitmap(MCanvas* canvas, _m_byte alpha, _m_rect dest, _m_rect src, bool highQuality) + { + if (dest.IsEmpty()) + dest = { 0, 0, int(canvas->GetWidth()), int(canvas->GetHeight()) }; + + if (src.IsEmpty()) + src = { 0, 0, int(canvas->GetWidth()), int(canvas->GetHeight()) }; + + drawBitmap(static_cast(canvas)->m_hdc, alpha, dest, src, highQuality); + } + + void MRender_GDIP::DrawBatchBitmap(MBatchBitmap* bmp, MBitmap* input, bool highQuality) + { + } + + void MRender_GDIP::DrawBatchBitmap(MBatchBitmap* bmp, MCanvas* input, bool highQuality) + { + } + + void MRender_GDIP::DrawNinePalacesImg(MBitmap* img, _m_byte alpha, _m_rect dest, _m_rect src, _m_rect margin, + bool highQuality) + { + if (dest.IsEmpty()) + dest = { 0, 0, int(img->GetWidth()), int(img->GetHeight()) }; + + if (src.IsEmpty()) + src = { 0, 0, int(img->GetWidth()), int(img->GetHeight()) }; + + NinePalaceDraw([&](_m_rect _dst, _m_rect _src) + { + drawBitmap(static_cast(img)->m_hdc, alpha, _dst, _src, highQuality); + }, dest, src, margin); + } + + void MRender_GDIP::DrawNinePalacesImg(MCanvas* canvas, _m_byte alpha, _m_rect dest, _m_rect src, _m_rect margin, + bool highQuality) + { + if (dest.IsEmpty()) + dest = { 0, 0, int(canvas->GetWidth()), int(canvas->GetHeight()) }; + + if (src.IsEmpty()) + + src = { 0, 0, int(canvas->GetWidth()), int(canvas->GetHeight()) }; + + NinePalaceDraw([&](_m_rect _dst, _m_rect _src) + { + drawBitmap(static_cast(canvas)->m_hdc, alpha, _dst, _src, highQuality); + }, dest, src, margin); + } + + void MRender_GDIP::DrawRectangle(_m_rect dest, MPen* pen) + { + int penWidth = (int)static_cast(pen)->m_pen->GetWidth(); + dest.left += penWidth / 2; + dest.bottom += penWidth / 2; + dest.right -= penWidth; + dest.bottom -= penWidth; + + m_Graphics->SetSmoothingMode(SmoothingModeHighSpeed); + m_Graphics->DrawRectangle(static_cast(pen)->m_pen, dest.left, dest.top, dest.GetWidth(), dest.GetHeight()); + } + + void MRender_GDIP::DrawRoundedRect(_m_rect dest, float round, MPen* pen) + { + RAII::Mui_Ptr geometry = CreateRoundGeometry(dest, round); + m_Graphics->SetSmoothingMode(SmoothingModeHighQuality); + m_Graphics->DrawPath(static_cast(pen)->m_pen, geometry.cast()->m_geometry); + } + + void MRender_GDIP::FillRectangle(_m_rect dest, MBrush* brush) + { + m_Graphics->SetSmoothingMode(SmoothingModeHighSpeed); + m_Graphics->FillRectangle(static_cast(brush)->m_brush, dest.left, dest.top, dest.GetWidth(), dest.GetHeight()); + } + + void MRender_GDIP::FillRectangle(_m_rect dest, MGradientBrush* brush) + { + m_Graphics->SetSmoothingMode(SmoothingModeHighSpeed); + auto gbrush = static_cast(brush)->m_brush; + gbrush->TranslateTransform((float)dest.left, (float)dest.top, MatrixOrderAppend); + m_Graphics->FillRectangle(gbrush, dest.left, dest.top, dest.GetWidth(), dest.GetHeight()); + gbrush->ResetTransform(); + } + + void MRender_GDIP::FillRoundedRect(_m_rect dest, float round, MBrush* brush) + { + RAII::Mui_Ptr geometry = CreateRoundGeometry(dest, round); + m_Graphics->SetSmoothingMode(SmoothingModeHighQuality); + m_Graphics->FillPath(static_cast(brush)->m_brush, geometry.cast()->m_geometry); + } + + void MRender_GDIP::DrawTextLayout(MFont* font, _m_rect dest, MBrush* brush, TextAlign alignment) + { + m_Graphics->SetTextRenderingHint(TextRenderingHintAntiAlias); + m_Graphics->SetSmoothingMode(SmoothingModeHighQuality); + m_Graphics->SetInterpolationMode(InterpolationModeHighQualityBicubic); + m_Graphics->SetPixelOffsetMode(PixelOffsetModeHighQuality); + + StringFormat format; + if (alignment & TextAlign_Top) + format.SetAlignment(StringAlignmentNear); + if (alignment & TextAlign_Left) + format.SetAlignment(StringAlignmentNear); + if (alignment & TextAlign_Center) + format.SetAlignment(StringAlignmentCenter); + if (alignment & TextAlign_Right) + format.SetAlignment(StringAlignmentFar); + if (alignment & TextAlign_VCenter) + format.SetLineAlignment(StringAlignmentCenter); + if (alignment & TextAlign_Bottom) + { + format.SetAlignment(StringAlignmentFar); + format.SetLineAlignment(StringAlignmentFar); + } + format.SetFormatFlags(StringFormatFlagsNoWrap); + format.SetTrimming(StringTrimmingNone); + + auto fontObj = static_cast(font); + m_Graphics->DrawString(fontObj->m_text.cstr(), -1, + fontObj->m_fontObj, RectF((float)dest.left, (float)dest.top, (float)dest.GetWidth(), (float)dest.GetHeight()), + &format, static_cast(brush)->m_brush); + } + + void MRender_GDIP::DrawBitmapEffects(MBitmap* img, MEffects* effect, _m_byte alpha, _m_rect dest, _m_rect src) + { + } + + void MRender_GDIP::DrawBitmapEffects(MCanvas* canvas, MEffects* effect, _m_byte alpha, _m_rect dest, _m_rect src) + { + } + + void MRender_GDIP::DrawLine(UIPoint x, UIPoint y, MPen* pen) + { + m_Graphics->SetSmoothingMode(SmoothingModeAntiAlias); + m_Graphics->DrawLine(static_cast(pen)->m_pen, x.x, x.y, y.x, y.y); + } + + void MRender_GDIP::DrawEllipse(_m_rect dest, MPen* pen) + { + m_Graphics->DrawEllipse(static_cast(pen)->m_pen, + RectF((float)dest.left, (float)dest.top, (float)dest.GetWidth(), (float)dest.GetHeight())); + } + + void MRender_GDIP::FillEllipse(_m_rect dest, MBrush* brush) + { + m_Graphics->FillEllipse(static_cast(brush)->m_brush, + RectF((float)dest.left, (float)dest.top, (float)dest.GetWidth(), (float)dest.GetHeight())); + } + + void MRender_GDIP::PushClipRect(_m_rect rect) + { + m_Graphics->SetClip(Rect(rect.left, rect.top, rect.GetWidth(), rect.GetHeight())); + } + + void MRender_GDIP::PopClipRect() + { + m_Graphics->ResetClip(); + } + + void MRender_GDIP::PushClipGeometry(MGeometry* geometry) + { + m_state = m_Graphics->Save(); + m_Graphics->SetClip(static_cast(geometry)->m_geometry); + } + + void MRender_GDIP::PopClipGeometry() + { + m_Graphics->Restore(m_state); + } + + void MRender_GDIP::Clear(_m_color color) + { + m_Graphics->Clear(Gdiplus::Color(color)); + } + + _m_param MRender_GDIP::GetDC() + { + //m_hdc = m_Graphics->GetHDC(); + return (_m_param)m_Canvas->m_hdc; + } + + void MRender_GDIP::ReleaseDC() + { + /*m_Graphics->ReleaseHDC(m_hdc); + m_hdc = nullptr;*/ + } + + _m_result MRender_GDIP::EndDraw() + { + return 0; + } + + MCanvas* MRender_GDIP::GetCanvas() + { + return m_Canvas.get(); + } + + MCanvas* MRender_GDIP::GetRenderCanvas() + { + return m_CanvasDef.get(); + } + + void* MRender_GDIP::Get() + { + return m_Graphics.get(); + } + + void MRender_GDIP::Flush() + { + } + + MCanvas* MRender_GDIP::GetSharedCanvas() + { + return m_CanvasTmp.get(); + } + + bool MRender_GDIP::SaveMBitmap(MBitmap* bitmap, std::wstring_view path, MImgFormat format) + { + if (!bitmap) return false; + + Gdiplus::Bitmap* gbitmap = Gdiplus::Bitmap::FromHBITMAP(static_cast(bitmap)->m_bitmap, nullptr); + auto clean = RAII::scope_exit([&] { delete gbitmap; }); + + const CLSID* clsid = GetEncoderClsid(format); + if (!clsid) return false; + + return gbitmap->Save(path.data(), clsid, nullptr) == Ok; + } + + bool MRender_GDIP::SaveMCanvas(MCanvas* canvas, std::wstring_view path, MImgFormat format) + { + if (!canvas) return false; + + Gdiplus::Bitmap* gbitmap = Gdiplus::Bitmap::FromHBITMAP(static_cast(canvas)->m_bitmap, nullptr); + auto clean = RAII::scope_exit([&] { delete gbitmap; }); + + + const CLSID* clsid = GetEncoderClsid(format); + if (!clsid) return false; + + return gbitmap->Save(path.data(), clsid, nullptr) == Ok; + } + + UIResource MRender_GDIP::SaveMBitmap(MBitmap* bitmap, MImgFormat format) + { + return saveBitmap(static_cast(bitmap)->m_bitmap, format); + } + + UIResource MRender_GDIP::SaveMCanvas(MCanvas* canvas, MImgFormat format) + { + return saveBitmap(static_cast(canvas)->m_bitmap, format); + } + + bool MRender_GDIP::CheckSubAtlasSource(MCanvas* canvas, MCanvas* canvas1) + { + return false; + } + + BOOL MRender_GDIP::MAlphaBlend(HDC hdcDest, int xDest, int yDest, int wDest, int hDest, HDC hdcSrc, int xSrc, + int ySrc, int wSrc, int hSrc, BYTE alpha, MRgn_GDIP* mRgn) + { + if(!hdcDest || !hdcSrc) + return FALSE; + + HBITMAP dstBitmap = (HBITMAP)GetCurrentObject(hdcDest, OBJ_BITMAP); + BITMAP dstBmpInfo; + GetObjectW(dstBitmap, sizeof(BITMAP), &dstBmpInfo); + auto* dstPixels = static_cast(dstBmpInfo.bmBits); + + if(dstBmpInfo.bmBitsPixel != 32) + return FALSE; + + HBITMAP srcBitmap = (HBITMAP)GetCurrentObject(hdcSrc, OBJ_BITMAP); + BITMAP srcBmpInfo; + GetObjectW(srcBitmap, sizeof(BITMAP), &srcBmpInfo); + auto* srcPixels = static_cast(srcBmpInfo.bmBits); + + if(srcBmpInfo.bmBitsPixel != 32) + return FALSE; + + ySrc = srcBmpInfo.bmHeight - ySrc - hSrc; + int yDestInv = dstBmpInfo.bmHeight - yDest - hDest; + + float scaleX = static_cast(wSrc) / (float)wDest; + float scaleY = static_cast(hSrc) / (float)hDest; + + for (int y = 0; y < hDest; ++y) + { + for (int x = 0; x < wDest; ++x) + { + int srcX = static_cast((float)x * scaleX); + int srcY = static_cast((float)y * scaleY); + + if(xDest + x < 0 || xDest + x >= dstBmpInfo.bmWidth || yDestInv + y < 0 || yDestInv + y >= dstBmpInfo.bmHeight) + continue; + + if(mRgn) + { + _m_byte maskBit = mRgn->GetBit(xDest + x, yDestInv + y); + if(maskBit == 0) + continue; + } + + BYTE* srcPixel = srcPixels + (srcY + ySrc) * srcBmpInfo.bmWidthBytes + (srcX + xSrc) * 4; + BYTE* dstPixel = dstPixels + (y + yDestInv) * dstBmpInfo.bmWidthBytes + (x + xDest) * 4; + + //Alpha 混合 + BYTE srcAlpha = (srcPixel[3] * alpha) / 255; + BYTE invAlpha = 255 - srcAlpha; + dstPixel[0] = (srcPixel[0] * srcAlpha + dstPixel[0] * invAlpha) / 255; + dstPixel[1] = (srcPixel[1] * srcAlpha + dstPixel[1] * invAlpha) / 255; + dstPixel[2] = (srcPixel[2] * srcAlpha + dstPixel[2] * invAlpha) / 255; + dstPixel[3] = (srcAlpha + dstPixel[3] * invAlpha / 255); + } + } + return TRUE; + } + + MRgn_GDIP* MRender_GDIP::GetMRgn() + { + Region region; + m_Graphics->GetClip(®ion); + HRGN hRGN = region.GetHRGN(m_Graphics.get()); + m_Rgn->Set(hRGN); + DeleteObject(hRGN); + return m_Rgn.get(); + } + + void MRender_GDIP::ReleaseThis() + { + } + + void MRender_GDIP::drawBitmap(HDC hdc, _m_byte alpha, _m_rect dest, _m_rect src, bool highQuality) + { + //SelectClipRgn(m_Canvas->m_hdc, hRGN); + + auto rgn = GetMRgn(); + MAlphaBlend(m_Canvas->m_hdc, dest.left, dest.top, dest.GetWidth(), dest.GetHeight(), + hdc, src.left, src.top, src.GetWidth(), src.GetHeight(), alpha, rgn); + + //SelectClipRgn(m_Canvas->m_hdc, nullptr); + + /*Bitmap img(hbitmap, nullptr); + + if (dest.IsEmpty()) + dest = { 0, 0, int(img.GetWidth()), int(img.GetHeight()) }; + + if (src.IsEmpty()) + src = { 0, 0, int(img.GetWidth()), int(img.GetHeight()) }; + + Rect destGdiRect(dest.left, dest.top, dest.GetWidth(), dest.GetHeight()); + Rect srcGdiRect(src.left, src.top, src.GetWidth(), src.GetHeight()); + + m_Graphics->SetSmoothingMode(highQuality ? SmoothingModeHighQuality : SmoothingModeHighSpeed); + + if (alpha == 255) + { + m_Graphics->DrawImage(&img, destGdiRect, srcGdiRect.X, srcGdiRect.Y, + srcGdiRect.Width, srcGdiRect.Height, UnitPixel); + } + else + { + ImageAttributes imageAttributes; + ColorMatrix colorMatrix = + { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, (float)alpha / 255.f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 1.0f + }; + imageAttributes.SetColorMatrix(&colorMatrix, ColorMatrixFlagsDefault, ColorAdjustTypeBitmap); + m_Graphics->DrawImage(&img, destGdiRect, srcGdiRect.X, srcGdiRect.Y, + srcGdiRect.Width, srcGdiRect.Height, UnitPixel, &imageAttributes); + }*/ + } + + UIResource MRender_GDIP::saveBitmap(HBITMAP hbitmap, MImgFormat format) + { + Gdiplus::Bitmap* bitmap = Gdiplus::Bitmap::FromHBITMAP(hbitmap, nullptr); + auto clean = RAII::scope_exit([&] { delete bitmap; }); + + if (!bitmap) return { nullptr, 0 }; + + const CLSID* clsid = GetEncoderClsid(format); + if (!clsid) return { nullptr, 0 }; + + IStream* stream = SHCreateMemStream(nullptr, 0); + if (!stream) return { nullptr, 0 }; + + Status status = bitmap->Save(stream, clsid, nullptr); + if (status != Ok) + { + stream->Release(); + return { nullptr, 0 }; + } + + ULARGE_INTEGER size; + LARGE_INTEGER zero = { 0 }; + stream->Seek(zero, STREAM_SEEK_END, &size); + stream->Seek(zero, STREAM_SEEK_SET, nullptr); + + std::vector<_m_byte> data(size.LowPart); + ULONG read; + stream->Read(data.data(), size.LowPart, &read); + stream->Release(); + + return { data.data(), size.LowPart }; + } +} diff --git a/MiaoUI/src/source/Render/Graphs/Mui_Render.cpp b/MiaoUI/src/source/Render/Graphs/Mui_Render.cpp new file mode 100644 index 0000000..04afeec --- /dev/null +++ b/MiaoUI/src/source/Render/Graphs/Mui_Render.cpp @@ -0,0 +1,104 @@ +/** + * FileName: Mui_Render.cpp + * Note: 渲染功能助手实现 + * + * Copyright (C) 2020-2022 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2020-10-17 Create +*/ + +#include + +namespace Mui::Render +{ + void NinePalaceDraw(const std::function& callback, _m_rect dest, _m_rect src, _m_rect margin) + { + //copy from soui + int xDest[4] = { dest.left, dest.left + margin.left, dest.right - margin.right, dest.right }; + int xSrc[4] = { src.left, src.left + margin.left, src.right - margin.right, src.right }; + int yDest[4] = { dest.top, dest.top + margin.top, dest.bottom - margin.bottom, dest.bottom }; + int ySrc[4] = { src.top, src.top + margin.top, src.bottom - margin.bottom, src.bottom }; + + //首先保证九宫分割正常 + if (!(xSrc[0] <= xSrc[1] && xSrc[1] <= xSrc[2] && xSrc[2] <= xSrc[3])) return; + if (!(ySrc[0] <= ySrc[1] && ySrc[1] <= ySrc[2] && ySrc[2] <= ySrc[3])) return; + + //调整目标位置 + int nDestWid = dest.right - dest.left; + int nDestHei = dest.bottom - dest.top; + + if ((margin.left + margin.right) > nDestWid) + {//边缘宽度大于目标宽度的处理 + if (margin.left >= nDestWid) + {//只绘制左边部分 + xSrc[1] = xSrc[2] = xSrc[3] = xSrc[0] + nDestWid; + xDest[1] = xDest[2] = xDest[3] = xDest[0] + nDestWid; + } + else if (margin.right >= nDestWid) + {//只绘制右边部分 + xSrc[0] = xSrc[1] = xSrc[2] = xSrc[3] - nDestWid; + xDest[0] = xDest[1] = xDest[2] = xDest[3] - nDestWid; + } + else + {//先绘制左边部分,剩余的用右边填充 + int nRemain = xDest[3] - xDest[1]; + xSrc[2] = xSrc[3] - nRemain; + xDest[2] = xDest[3] - nRemain; + } + } + + if (margin.top + margin.bottom > nDestHei) + { + if (margin.top >= nDestHei) + {//只绘制上边部分 + ySrc[1] = ySrc[2] = ySrc[3] = ySrc[0] + nDestHei; + yDest[1] = yDest[2] = yDest[3] = yDest[0] + nDestHei; + } + else if (margin.bottom >= nDestHei) + {//只绘制下边部分 + ySrc[0] = ySrc[1] = ySrc[2] = ySrc[3] - nDestHei; + yDest[0] = yDest[1] = yDest[2] = yDest[3] - nDestHei; + } + else + {//先绘制左边部分,剩余的用右边填充 + int nRemain = yDest[3] - yDest[1]; + ySrc[2] = ySrc[3] - nRemain; + yDest[2] = yDest[3] - nRemain; + } + } + + for (int y = 0; y < 3; y++) + { + if (ySrc[y] == ySrc[y + 1]) continue; + for (int x = 0; x < 3; x++) + { + if (xSrc[x] == xSrc[x + 1]) continue; + _m_rect rcSrc; + rcSrc.left = xSrc[x]; + rcSrc.top = ySrc[y]; + rcSrc.right = xSrc[x + 1]; + rcSrc.bottom = ySrc[y + 1]; + + _m_rect rcDest; + rcDest.left = xDest[x]; + rcDest.top = yDest[y]; + rcDest.right = xDest[x + 1]; + rcDest.bottom = yDest[y + 1]; + + callback(rcDest, rcSrc); + } + } + } +} diff --git a/MiaoUI/src/source/Render/Graphs/Mui_RenderDef.cpp b/MiaoUI/src/source/Render/Graphs/Mui_RenderDef.cpp new file mode 100644 index 0000000..bd6645b --- /dev/null +++ b/MiaoUI/src/source/Render/Graphs/Mui_RenderDef.cpp @@ -0,0 +1,56 @@ +/** + * FileName: Mui_RenderDef.cpp + * Note: UI渲染接口基本方法实现 + * + * Copyright (C) 2022 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-8-13 Create +*/ + +#include +#include + +namespace Mui::Render::Def +{ + void MRenderObj::ReleaseProc() + { + //如果是渲染线程 可以直接调用释放 + if (IsBaseThread()) + { + ReleaseThis(); + return; + } + //否则需要将释放任务交给渲染线程调用 + static_cast(m_base)->Task([this] + { + ReleaseThis(); + }); + } + + bool MRenderObj::IsBaseThread() + { + return static_cast(m_base)->IsTaskThread(); + } + + bool MRenderObj::IsDrawing() + { + return static_cast(m_base)->m_begindraw; + } + + void* MRenderObj::GetBaseRender() + { + return static_cast(m_base)->m_base; + } +} diff --git a/MiaoUI/src/source/Render/Mui_RenderMgr.cpp b/MiaoUI/src/source/Render/Mui_RenderMgr.cpp new file mode 100644 index 0000000..d2efb7a --- /dev/null +++ b/MiaoUI/src/source/Render/Mui_RenderMgr.cpp @@ -0,0 +1,617 @@ +/** + * FileName: Mui_RenderMgr.cpp + * Note: 渲染命令管理器实现 + * + * Copyright (C) 2021-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2021-12-25 Create +*/ + +#include + +namespace Mui::Render +{ + using namespace RAII; + + MRenderCmd::MRenderCmd(MRender* base) + { + m_stop = false; + m_begindraw = false; + m_base = base; + m_base->m_base = this; + m_thread = std::thread(&MRenderCmd::ThreadProc, this); + } + + MRenderCmd::~MRenderCmd() + { + m_stop = true; + m_signal.notify_one(); + m_thread.join(); + } + + _m_lpcwstr MRenderCmd::GetRenderName() + { + return m_base->GetRenderName(); + } + + bool MRenderCmd::InitRender(_m_uint width, _m_uint height) + { + bool ret = false; + Task([&] + { + ret = m_base->InitRender(width, height); + }); + return ret; + } + + bool MRenderCmd::Resize(_m_uint width, _m_uint height) + { + bool ret = false; + Task([&] + { + ret = m_base->Resize(width, height); + }); + return ret; + } + + MCanvasPtr MRenderCmd::CreateCanvas(_m_uint width, _m_uint height, _m_param param) + { + MCanvasPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateCanvas(width, height, param); + if(ret) ret->m_base = this; + }); + return ret; + } + + MBitmapPtr MRenderCmd::CreateBitmap(std::wstring_view path, _m_param param) + { + MBitmapPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateBitmap(path, param); + if (ret) ret->m_base = this; + }); + return ret; + } + + MBitmapPtr MRenderCmd::CreateBitmap(UIResource resource, _m_param param) + { + MBitmapPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateBitmap(resource, param); + if (ret) ret->m_base = this; + }); + return ret; + } + + MBitmapPtr MRenderCmd::CreateBitmap(_m_uint width, _m_uint height, void* bit, _m_uint len, _m_uint stride) + { + MBitmapPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateBitmap(width, height, bit, len, stride); + if (ret) ret->m_base = this; + }); + return ret; + } + + MBitmapPtr MRenderCmd::CreateSVGBitmap(std::wstring_view path, _m_uint width, _m_uint height, bool repColor, _m_color color) + { + MBitmapPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateSVGBitmap(path, width, height, repColor, color); + if (ret) ret->m_base = this; + }); + return ret; + } + + MBitmapPtr MRenderCmd::CreateSVGBitmapFromXML(std::wstring_view xml, _m_uint width, _m_uint height, bool repColor, + _m_color color) + { + MBitmapPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateSVGBitmapFromXML(xml, width, height, repColor, color); + if (ret) ret->m_base = this; + }); + return ret; + } + + MPenPtr MRenderCmd::CreatePen(_m_uint width, _m_color color) + { + MPenPtr ret = nullptr; + Task([&] + { + ret = m_base->CreatePen(width, color); + if (ret) ret->m_base = this; + }); + return ret; + } + + MBrushPtr MRenderCmd::CreateBrush(_m_color color) + { + MBrushPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateBrush(color); + if (ret) ret->m_base = this; + }); + return ret; + } + + MGradientBrushPtr MRenderCmd::CreateGradientBrush(const std::pair<_m_color, float>* vertex, _m_ushort count, UIPoint start, UIPoint end) + { + MGradientBrushPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateGradientBrush(vertex, count, start, end); + if (ret) ret->m_base = this; + }); + return ret; + } + + MFontPtr MRenderCmd::CreateFonts(std::wstring_view text, std::wstring_view fontName, _m_uint fontSize, _m_ptrv fontCollection) + { + MFontPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateFonts(text, fontName, fontSize, fontCollection); + if (ret) ret->m_base = this; + }); + return ret; + } + + MEffectPtr MRenderCmd::CreateEffects(MEffects::Types effect, float value) + { + MEffectPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateEffects(effect, value); + if (ret) ret->m_base = this; + }); + return ret; + } + + MGeometryPtr MRenderCmd::CreateRoundGeometry(_m_rect dest, float round) + { + MGeometryPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateRoundGeometry(dest, round); + if (ret) ret->m_base = this; + }); + return ret; + } + + MGeometryPtr MRenderCmd::CreateEllipseGeometry(_m_rect dest) + { + MGeometryPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateEllipseGeometry(dest); + if (ret) ret->m_base = this; + }); + return ret; + } + + MCanvasPtr MRenderCmd::CreateSubAtlasCanvas(_m_uint width, _m_uint height) + { + MCanvasPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateSubAtlasCanvas(width, height); + if (ret) ret->m_base = this; + }); + return ret; + } + + MBatchBitmapPtr MRenderCmd::CreateBatchBitmap() + { + MBatchBitmapPtr ret = nullptr; + Task([&] + { + ret = m_base->CreateBatchBitmap(); + if (ret) ret->m_base = this; + }); + return ret; + } + + bool MRenderCmd::CopyBitmapContent(MBitmap* dst, MBitmap* src, UIPoint dstPt, _m_rect srcRect) + { + bool ret = false; + Task([&] + { + ret = m_base->CopyBitmapContent(dst, src, dstPt, srcRect); + }); + return ret; + } + + bool MRenderCmd::CopyBitmapContent(MCanvas* dst, MCanvas* src, UIPoint dstPt, _m_rect srcRect) + { + bool ret = false; + Task([&] + { + ret = m_base->CopyBitmapContent(dst, src, dstPt, srcRect); + }); + return ret; + } + + void MRenderCmd::BeginDraw() + { + if (m_begindraw) + _M_OutErrorDbg_(L"status error", true); + else + { + m_begindraw = true; + Task([=] + { + m_base->BeginDraw(); + }); + } + } + + void MRenderCmd::SetCanvas(MCanvasPtr canvas) + { + Task([&] + { + m_base->SetCanvas(canvas.get()); + }); + } + + void MRenderCmd::ResetCanvas() + { + Task([this] + { + m_base->ResetCanvas(); + }); + } + + void MRenderCmd::DrawBitmap(MBitmapPtr img, _m_byte alpha, _m_rect dest, _m_rect src, bool highQuality) + { + Task([&] + { + m_base->DrawBitmap(img.get(), alpha, dest, src, highQuality); + }); + } + + void MRenderCmd::DrawBitmap(MCanvasPtr canvas, _m_byte alpha, _m_rect dest, _m_rect src, bool highQuality) + { + Task([&] + { + m_base->DrawBitmap(canvas.get(), alpha, dest, src, highQuality); + }); + } + + void MRenderCmd::DrawBatchBitmap(MBatchBitmapPtr bmp, MBitmapPtr input, bool highQuality) + { + Task([&] + { + m_base->DrawBatchBitmap(bmp.get(), input.get(), highQuality); + }); + } + + void MRenderCmd::DrawBatchBitmap(MBatchBitmapPtr bmp, MCanvasPtr input, bool highQuality) + { + Task([&] + { + m_base->DrawBatchBitmap(bmp.get(), input.get(), highQuality); + }); + } + + void MRenderCmd::DrawNinePalacesImg(MBitmapPtr img, _m_byte alpha, _m_rect dest, _m_rect src, _m_rect margin, bool highQuality) + { + Task([&] + { + m_base->DrawNinePalacesImg(img.get(), alpha, dest, src, margin, highQuality); + }); + } + + void MRenderCmd::DrawNinePalacesImg(MCanvasPtr canvas, _m_byte alpha, _m_rect dest, _m_rect src, _m_rect margin, bool highQuality) + { + Task([&] + { + m_base->DrawNinePalacesImg(canvas.get(), alpha, dest, src, margin, highQuality); + }); + } + + void MRenderCmd::DrawRectangle(_m_rect dest, MPenPtr pen) + { + Task([&] + { + m_base->DrawRectangle(dest, pen.get()); + }); + } + + void MRenderCmd::DrawRoundedRect(_m_rect dest, float round, MPenPtr pen) + { + Task([&] + { + m_base->DrawRoundedRect(dest, round, pen.get()); + }); + } + + void MRenderCmd::FillRectangle(_m_rect dest, MBrushPtr brush) + { + Task([&] + { + m_base->FillRectangle(dest, brush.get()); + }); + } + + void MRenderCmd::FillRectangle(_m_rect dest, MGradientBrushPtr brush) + { + Task([&] + { + m_base->FillRectangle(dest, brush.get()); + }); + } + + void MRenderCmd::FillRoundedRect(_m_rect dest, float round, MBrushPtr brush) + { + Task([&] + { + m_base->FillRoundedRect(dest, round, brush.get()); + }); + } + + void MRenderCmd::DrawTextLayout(MFontPtr font, _m_rect dest, MBrushPtr brush, TextAlign alignment) + { + Task([&] + { + m_base->DrawTextLayout(font.get(), dest, brush.get(), alignment); + }); + } + + void MRenderCmd::DrawBitmapEffects(MBitmapPtr img, MEffectPtr effect, _m_byte alpha, _m_rect dest, _m_rect src) + { + Task([&] + { + m_base->DrawBitmapEffects(img.get(), effect.get(), alpha, dest, src); + }); + } + + void MRenderCmd::DrawBitmapEffects(MCanvasPtr canvas, MEffectPtr effect, _m_byte alpha, _m_rect dest, _m_rect src) + { + Task([&] + { + m_base->DrawBitmapEffects(canvas.get(), effect.get(), alpha, dest, src); + }); + } + + void MRenderCmd::DrawLine(UIPoint x, UIPoint y, MPenPtr pen) + { + Task([&] + { + m_base->DrawLine(x, y, pen.get()); + }); + } + + void MRenderCmd::DrawEllipse(_m_rect dest, MPenPtr pen) + { + Task([&] + { + m_base->DrawEllipse(dest, pen.get()); + }); + } + + void MRenderCmd::FillEllipse(_m_rect dest, MBrushPtr brush) + { + Task([&] + { + m_base->FillEllipse(dest, brush.get()); + }); + } + + void MRenderCmd::PushClipRect(_m_rect rect) + { + Task([&] + { + m_base->PushClipRect(rect); + }); + } + + void MRenderCmd::PopClipRect() + { + Task([this] + { + m_base->PopClipRect(); + }); + } + + void MRenderCmd::PushClipGeometry(MGeometryPtr geometry) + { + Task([&] + { + m_base->PushClipGeometry(geometry.get()); + }); + } + + void MRenderCmd::PopClipGeometry() + { + Task([&] + { + m_base->PopClipGeometry(); + }); + } + + void MRenderCmd::Clear(_m_color color) + { + Task([&] + { + m_base->Clear(color); + }); + } + + _m_result MRenderCmd::EndDraw() + { + _m_result ret = 0; + Task([&] + { + ret = m_base->EndDraw(); + }); + m_begindraw = false; + return ret; + } + + MCanvas* MRenderCmd::GetCanvas() + { + MCanvas* ret = nullptr; + Task([&] + { + ret = m_base->GetCanvas(); + }); + return ret; + } + + MCanvas* MRenderCmd::GetRenderCanvas() + { + MCanvas* ret = nullptr; + Task([&] + { + ret = m_base->GetRenderCanvas(); + }); + return ret; + } + + void* MRenderCmd::Get() + { + void* ret = nullptr; + Task([&] + { + ret = m_base->Get(); + }); + return ret; + } + + void MRenderCmd::Flush() + { + Task([&] + { + m_base->Flush(); + }); + } + + MCanvas* MRenderCmd::GetSharedCanvas() + { + MCanvas* ret = nullptr; + Task([&] + { + ret = m_base->GetSharedCanvas(); + }); + return ret; + } + + bool MRenderCmd::SaveMBitmap(MBitmapPtr bitmap, std::wstring path, MImgFormat format) + { + bool ret = false; + Task([&] + { + ret = m_base->SaveMBitmap(bitmap.get(), path, format); + }); + return ret; + } + + bool MRenderCmd::SaveMCanvas(MCanvasPtr canvas, std::wstring path, MImgFormat format) + { + bool ret = false; + Task([&] + { + ret = m_base->SaveMCanvas(canvas.get(), path, format); + }); + return ret; + } + + UIResource MRenderCmd::SaveMBitmap(MBitmapPtr bitmap, MImgFormat format) + { + UIResource ret; + Task([&] + { + ret = m_base->SaveMBitmap(bitmap.get(), format); + }); + return ret; + } + + UIResource MRenderCmd::SaveMCanvas(MCanvasPtr canvas, MImgFormat format) + { + UIResource ret; + Task([&] + { + ret = m_base->SaveMCanvas(canvas.get(), format); + }); + return ret; + } + + bool MRenderCmd::CheckSubAtlasSource(MCanvasPtr canvas, MCanvasPtr canvas1) + { + bool ret = false; + Task([&] + { + ret = m_base->CheckSubAtlasSource(canvas.get(), canvas1.get()); + }); + return ret; + } + + void MRenderCmd::ThreadProc() + { + while (!m_stop) + { + std::unique_lock lock(m_tasklock); + while (m_taskList.empty() && !m_stop) + m_signal.wait(lock); + + for (auto& task_item : m_taskList) + { + //传递异常到目标等待线程 + try + { + task_item.task(); + task_item.notification.set_value(); + } + catch(...) + { + task_item.notification.set_exception(std::current_exception()); + } + } + m_taskList.clear(); + } + } + + void MRenderCmd::Task(std::function&& task) + { + if (IsTaskThread()) + { + task(); + return; + } + std::exception_ptr ex = nullptr; + std::promise send_notification; + auto wait_notification = send_notification.get_future(); + { + std::unique_lock lock(m_tasklock); + m_taskList.emplace_back(task, send_notification); + } + m_signal.notify_one(); + wait_notification.get(); + } + + bool MRenderCmd::IsTaskThread() + { + return m_thread.get_id() == std::this_thread::get_id(); + } +} diff --git a/MiaoUI/src/source/Render/Node/Mui_Layout.cpp b/MiaoUI/src/source/Render/Node/Mui_Layout.cpp new file mode 100644 index 0000000..2ec74a6 --- /dev/null +++ b/MiaoUI/src/source/Render/Node/Mui_Layout.cpp @@ -0,0 +1,593 @@ +/** + * FileName: Mui_Layout.cpp + * Note: UI布局器 + * + * Copyright (C) 2020-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2023-4-2 Create +*/ +#include +#include +#include + +namespace Mui +{ + using namespace Render; + + void UILayouter::SetType(UIAlignment align) + { + m_type = align; + + m_linearParam_vertical = !(align == UIAlignment_LinearH || align == UIAlignment_LinearHL + || align == UIAlignment_LinearHLB || align == UIAlignment_LinearHB); + + m_linearParam_filp = align == UIAlignment_LinearHL || align == UIAlignment_LinearHLB + || align == UIAlignment_LinearVB || align == UIAlignment_LinearVBR; + + m_linearParam_swap = align == UIAlignment_LinearHLB || align == UIAlignment_LinearVBR + || align == UIAlignment_LinearHB || align == UIAlignment_LinearVR; + } + + void UILayouter::Layout(UINodeBase* node, size_t begin, bool calcper) + { + /*if(node->m_isAnimation) + { + AnimationLayout(node); + return; + }*/ + switch (m_type) + { + case UIAlignment_Block: + LayoutBlock(node, begin, calcper); + break; + case UIAlignment_LinearV: + case UIAlignment_LinearVB: + case UIAlignment_LinearVR: + case UIAlignment_LinearVBR: + case UIAlignment_LinearH: + case UIAlignment_LinearHB: + case UIAlignment_LinearHL: + case UIAlignment_LinearHLB: + LayoutLinear(node, begin, calcper); + break; + case UIAlignment_Absolute: + LayoutAbsolute(node, begin, calcper); + break; + case UIAlignment_Center: + LayoutCenter(node, calcper); + break; + case UIAlignment_Grid: + LayoutGrid(node); + break; + } + } + + void UILayouter::Intersect(_m_rectf* dst, const _m_rectf* rect1, const _m_rectf* rect2) + { + float rc1 = rect1->left; + if (rect1->left <= rect2->left) + rc1 = rect2->left; + dst->left = rc1; + + float rc2 = rect1->right; + if (rc2 >= rect2->right) + rc2 = rect2->right; + dst->right = rc2; + + if (Helper::M_DecimalEquals(rc1, rc2) || rc1 < rc2) + { + float rc3 = rect1->top; + if (rc3 <= rect2->top) + rc3 = rect2->top; + dst->top = rc3; + + float rc4 = rect1->bottom; + if (rc4 >= rect2->bottom) + rc4 = rect2->bottom; + dst->bottom = rc4; + + if (Helper::M_DecimalEquals(rc3, rc4) || rc3 < rc4) + { + return; + } + } + + dst->left = 0.0f; + dst->top = 0.0f; + dst->right = 0.0f; + dst->bottom = 0.0f; + } + + UINodeBase* UILayouter::GetPreviousNode(UINodeBase* node, size_t begin) + { + auto& list = node->GetNodeList(); + const size_t nodeCount = list.size(); + if (begin != 0 && begin < nodeCount) + { + //查找有效Node + for (_m_long64 i = (_m_long64)(begin - 1); i >= 0; --i) + { + if (i < 0) + break; + auto cast = (UINodeBase*)list[i]; + if (!cast || cast->m_data.HideThis) + continue; + return cast; + } + } + return nullptr; + } + + std::pair<_m_rectf, _m_rectf> UILayouter::CalcPadding(UINodeBase* node) + { + _m_rectf box = node->m_data.Frame; + auto padding = node->m_data.Padding.ToRectT(); + auto [xs, ys, ws, hs] = node->GetRectScale(); + box.left += _scale_to(padding.left, ws); + box.right -= _scale_to(padding.right, ws); + box.top += _scale_to(padding.top, hs); + box.bottom += _scale_to(padding.bottom, hs); + return std::make_pair(box, node->m_data.ClipFrame); + } + + _m_rectf& UILayouter::CalcBaseFrame(UINodeBase* node, const _m_rectf& box, _m_pointf& pt, _m_pointf start, bool calcper, int filp) + { + pt = node->GetCalcedPoint(); + _m_rectf& frame = node->m_data.Frame; + + if(filp == 1 || filp == 3) + frame.right = start.x - pt.x; + else + frame.left = start.x + pt.x; + + if(filp == 2 || filp == 3) + frame.bottom = start.y - pt.y; + else + frame.top = start.y + pt.y; + + //自动尺寸先给定父Size计算出子Size + if (node->m_data.AutoSize + || node->m_data.SizeUnit.x_w == UINodeBase::Percentage || node->m_data.SizeUnit.y_h == UINodeBase::Percentage + || node->m_data.SizeUnit.x_w == UINodeBase::FillMinus || node->m_data.SizeUnit.y_h == UINodeBase::FillMinus) + { + if(auto parent = node->GetValidParent()) + { + if (node->m_data.SizeUnit.x_w == UINodeBase::Percentage || node->m_data.SizeUnit.x_w == UINodeBase::FillMinus) + { + float sizeW = static_cast(node->m_data.Size.width) / 100.f; + if (node->m_data.SizeUnit.x_w == UINodeBase::FillMinus) + sizeW = 1.f; + if(filp == 1 || filp == 3) + frame.left = frame.right - (frame.right - parent->m_data.Frame.left) * sizeW; + else + frame.right = frame.left + (parent->m_data.Frame.right - frame.left) * sizeW; + } + else + { + if (filp == 1 || filp == 3) + frame.left = box.left; + else + frame.right = box.right; + } + if (node->m_data.SizeUnit.y_h == UINodeBase::Percentage || node->m_data.SizeUnit.y_h == UINodeBase::FillMinus) + { + float sizeH = static_cast(node->m_data.Size.height) / 100.f; + if (node->m_data.SizeUnit.y_h == UINodeBase::FillMinus) + sizeH = 1.f; + if(filp == 2 || filp == 3) + frame.top = frame.bottom - (frame.bottom - parent->m_data.Frame.top) * sizeH; + else + frame.bottom = frame.top + (parent->m_data.Frame.bottom - frame.top) * sizeH; + } + else + { + if (filp == 2 || filp == 3) + frame.top = box.top; + else + frame.bottom = box.bottom; + } + } + else + { + if(filp == 1 || filp == 3) + frame.left = box.left; + else + frame.right = box.right; + + if(filp == 2 || filp == 3) + frame.top = box.top; + else + frame.bottom = box.bottom; + } + } + if(node->m_data.AutoSize) + node->m_data.Align.Layout(node, 0, calcper); + return frame; + } + + _m_sizef UILayouter::CalcContentSize(UINodeBase* node, bool calcper) + { + _m_sizef ret; + _m_scale scale = { 1.f, 1.f }; + _m_scale nodescale; + bool minus = node->m_data.SizeUnit.x_w == UINodeBase::FillMinus || node->m_data.SizeUnit.y_h == UINodeBase::FillMinus; + if (minus) + { + scale = node->GetRectScale().scale(); + nodescale = scale; + } + else + nodescale = node->GetRectScale().scale(); + + //按百分比尺寸 + if(!node->m_data.AutoSize && + (node->m_data.SizeUnit.x_w == UINodeBase::Percentage || node->m_data.SizeUnit.y_h == UINodeBase::Percentage || minus)) + { + auto parent = node->GetValidParent(); + if (calcper && parent) + { + if (node->m_data.SizeUnit.x_w == UINodeBase::Percentage || node->m_data.SizeUnit.x_w == UINodeBase::FillMinus) + { + float sizeW = static_cast(node->m_data.Size.width) / 100.f; + if (node->m_data.SizeUnit.x_w == UINodeBase::FillMinus) + sizeW = 1.f; + + ret.width = parent->m_data.Frame.GetWidth(); + ret.width = ret.width * sizeW; + + if(minus) + ret.width -= _scale_to((float)node->m_data.Size.width, scale.cx); + } + else + ret.width = node->GetContentSize().width; + + if (node->m_data.SizeUnit.y_h == UINodeBase::Percentage || node->m_data.SizeUnit.y_h == UINodeBase::FillMinus) + { + float sizeH = static_cast(node->m_data.Size.height) / 100.f; + if (node->m_data.SizeUnit.y_h == UINodeBase::FillMinus) + sizeH = 1.f; + + ret.height = parent->m_data.Frame.GetHeight(); + ret.height = ret.height * sizeH; + + if (minus) + ret.height -= _scale_to((float)node->m_data.Size.height, scale.cy); + } + else + ret.height = node->GetContentSize().height; + } + else + { + if (node->m_data.SizeUnit.x_w == UINodeBase::Percentage || node->m_data.SizeUnit.x_w == UINodeBase::FillMinus) + { + ret.width = node->m_data.Frame.GetWidth(); + if(node->m_data.SizeUnit.x_w == UINodeBase::FillMinus) + ret.width -= _scale_to((float)node->m_data.Size.width, scale.cx); + } + else + ret.width = node->GetContentSize().width; + + if (node->m_data.SizeUnit.y_h == UINodeBase::Percentage || node->m_data.SizeUnit.y_h == UINodeBase::FillMinus) + { + ret.height = node->m_data.Frame.GetHeight(); + if(node->m_data.SizeUnit.y_h == UINodeBase::FillMinus) + ret.height -= _scale_to((float)node->m_data.Size.height, scale.cy); + } + else + ret.height = node->GetContentSize().height; + } + } + else + ret = node->GetContentSize(); + return SizeClamp(node, ret, nodescale); + } + + _m_sizef UILayouter::SizeClamp(UINodeBase* node, _m_sizef size, _m_scale scale) + { + _m_sizef minsize = { (float)node->m_data.MinSize.width, (float)node->m_data.MinSize.height }; + + minsize.width = _scale_to(minsize.width, scale.cx); + minsize.height = _scale_to(minsize.height, scale.cy); + + if (size.width < minsize.width) + size.width = minsize.width; + if (size.height < minsize.height) + size.height = minsize.height; + + _m_sizef maxsize = { (float)node->m_data.MaxSize.width, (float)node->m_data.MaxSize.height }; + if (!Helper::M_DecimalEquals(maxsize.width, -1.f)) + { + maxsize.width = _scale_to(maxsize.width, scale.cx); + if (size.width > maxsize.width) + size.width = maxsize.width; + } + if (!Helper::M_DecimalEquals(maxsize.height, -1.f)) + { + maxsize.height = _scale_to(maxsize.height, scale.cx); + if (size.height > maxsize.height) + size.height = maxsize.height; + } + + return size; + } + + void UILayouter::LayoutBlock(UINodeBase* node, size_t begin, bool calcper) + { + auto [box, clip] = CalcPadding(node); + //计算子NodeFrame + const float lineMaxR = box.right + 1.f; + float lineMaxH = 0, top = box.top, lastX = box.left; + + //获取上一个Node基准位置 + if(const auto pre = GetPreviousNode(node, begin); pre) + { + const _m_rectf& frame = pre->m_data.Frame; + lastX = frame.right; + top = frame.top; + } + + auto& list = node->GetNodeList(); + + //获取Node所在行的最大高度 + if(begin != 0) + { + for (size_t i = begin - 1; i > 0; --i) + { + auto cast = (UINodeBase*)list[i]; + if (!cast || cast->m_data.HideThis) + continue; + //确定和目标Node在同一行 + if (!Helper::M_DecimalEquals(top, cast->m_data.Frame.top)) + break; + lineMaxH = Helper::M_MAX(lineMaxH, cast->m_data.Frame.GetHeight()); + } + } + + //计算子Node Frame + const size_t nodeCount = list.size(); + for(size_t i = begin; i < nodeCount; ++i) + { + auto cast = (UINodeBase*)list[i]; + if (!cast) continue; + + _m_pointf pt; + _m_rectf& frame = CalcBaseFrame(cast, box, pt, { lastX, top }, calcper); + const _m_sizef _size = CalcContentSize(cast, calcper); + + frame.right = frame.left + _size.width; + frame.bottom = frame.top + _size.height; + + //本行已经无法容纳 从下一行开始 + if(frame.right > lineMaxR && !Helper::M_DecimalEquals(lineMaxH, 0.f)) //lineMaxH代表此Node是本行唯一一个 因此无需换行 + { + frame.left = box.left + pt.x; + frame.right = frame.left + _size.width; + frame.top += lineMaxH; + frame.bottom = frame.top + _size.height; + + top += lineMaxH; + lastX = frame.right; + lineMaxH = pt.y + _size.height; + } + else + { + lastX = frame.right; + lineMaxH = Helper::M_MAX(lineMaxH, pt.y + _size.height); + } + + Intersect(&cast->m_data.ClipFrame, &clip, &cast->m_data.Frame); + + cast->m_data.Align.Layout(cast, 0, false); + + if (!calcper) + cast->OnLayoutCalced(); + } + } + + void UILayouter::LayoutLinear(UINodeBase* node, size_t begin, bool calcper) + { + const bool horizontal = !m_linearParam_vertical; //是否为水平 + const bool filp = m_linearParam_filp; //是否上下反转或左右反转 + const bool swap = m_linearParam_swap; //是否左右反转或上下反转 + + auto [box, clip] = CalcPadding(node); + float last = horizontal ? (filp ? box.right : box.left) : (filp ? box.bottom : box.top); + + auto& list = node->GetNodeList(); + + //获取上一个Node基准位置 + if (begin != 0) + { + if (const auto pre = GetPreviousNode(node, begin); pre) + { + const _m_rectf& frame = pre->m_data.Frame; + last = horizontal ? (filp ? frame.left : frame.right) : (filp ? frame.top : frame.bottom); + } + } + int type = 0; + if (filp) + type = horizontal ? 1 : 2; + + const size_t nodeCount = list.size(); + for (size_t i = begin; i < nodeCount; ++i) + { + auto cast = (UINodeBase*)list[i]; + if (!cast) continue; + + _m_pointf pt; + _m_rectf& frame = CalcBaseFrame(cast, box, pt, + { + horizontal ? last : box.left, + horizontal ? box.top : last + }, + calcper, type + ); + _m_sizef _size = CalcContentSize(cast, calcper); + + if (horizontal) + { + //Right + if (filp) + { + frame.right = last - pt.x; + frame.left = frame.right - _size.width; + } + else + { + frame.left = last + pt.x; + frame.right = frame.left + _size.width; + } + + //Bottom + if (swap) + { + frame.bottom = box.bottom - pt.y; + frame.top = frame.bottom - _size.height; + } + else + { + frame.top = box.top + pt.y; + frame.bottom = frame.top + _size.height; + } + } + else + { + //Right + if (swap) + { + frame.right = box.right - pt.x; + frame.left = frame.right - _size.width; + } + else + { + frame.left = box.left + pt.x; + frame.right = frame.left + _size.width; + } + + //Bottom + if (filp) + { + frame.bottom = last - pt.y; + frame.top = frame.bottom - _size.height; + } + else + { + frame.top = last + pt.y; + frame.bottom = frame.top + _size.height; + } + } + + last = horizontal ? (filp ? frame.left : frame.right) : (filp ? frame.top : frame.bottom); + + Intersect(&cast->m_data.ClipFrame, &clip, &cast->m_data.Frame); + + cast->m_data.Align.Layout(cast, 0, false); + + if (!calcper) + cast->OnLayoutCalced(); + } + } + + void UILayouter::LayoutAbsolute(UINodeBase* node, size_t begin, bool calcper) + { + auto calcframe = CalcPadding(node); + auto& list = node->GetNodeList(); + const size_t nodeCount = list.size(); + + auto calc = [&list, &calcframe, &calcper, this](size_t index) + { + auto cast = (UINodeBase*)list[index]; + if (!cast) return; + + _m_pointf pt; + _m_rectf& frame = CalcBaseFrame(cast, calcframe.first, pt, { calcframe.first.left, calcframe.first.top }, calcper); + const _m_sizef _size = CalcContentSize(cast, calcper); + frame.right = frame.left + _size.width; + frame.bottom = frame.top + _size.height; + + Intersect(&cast->m_data.ClipFrame, &calcframe.second, &cast->m_data.Frame); + + cast->m_data.Align.Layout(cast, 0, false); + + if(!calcper) + cast->OnLayoutCalced(); + }; + + if (begin != 0 && begin < nodeCount) + calc(begin); + else + { + for (size_t i = 0; i < nodeCount; i++) + calc(i); + } + } + + void UILayouter::LayoutCenter(UINodeBase* node, bool calcper) + { + auto [box, clip] = CalcPadding(node); + const _m_sizef boxSize = { box.GetWidth(), box.GetHeight() }; + + for (auto& _node : node->GetNodeList()) + { + auto cast = (UINodeBase*)_node; + if (!cast) continue; + + _m_pointf pt; + _m_rectf& frame = CalcBaseFrame(cast, box, pt, { box.left, box.top }, calcper); + _m_sizef _size = CalcContentSize(cast, calcper); + + frame.left = box.left + (boxSize.width - _size.width) / 2.f + pt.x; + frame.top = box.top + (boxSize.height - _size.height) / 2.f + pt.y; + frame.right = frame.left + _size.width; + frame.bottom = frame.top + _size.height; + + Intersect(&cast->m_data.ClipFrame, &clip, &cast->m_data.Frame); + + cast->m_data.Align.Layout(cast, 0, false); + + if (!calcper) + cast->OnLayoutCalced(); + } + } + + void UILayouter::LayoutGrid(UINodeBase* node) + { + } + + void UILayouter::AnimationLayout(UINodeBase* node) + { + /*for (auto& _node : node->GetNodeList()) + { + auto cast = (UINodeBase*)_node; + + auto scale = cast->GetRectScale(); + cast->m_data.ClipFrame = + { + _scale_to(cast->aniClip.left, scale.xs), + _scale_to(cast->aniClip.top, scale.ys), + _scale_to(cast->aniClip.right, scale.ws), + _scale_to(cast->aniClip.bottom, scale.hs) + }; + cast->m_data.Frame = + { + _scale_to(cast->m_aniFrame.left, scale.xs), + _scale_to(cast->m_aniFrame.top, scale.ys), + _scale_to(cast->m_aniFrame.right, scale.ws), + _scale_to(cast->m_aniFrame.bottom, scale.hs) + }; + cast->m_data.Align.Layout(cast, 0, false); + }*/ + } +} diff --git a/MiaoUI/src/source/Render/Node/Mui_RenderNode.cpp b/MiaoUI/src/source/Render/Node/Mui_RenderNode.cpp new file mode 100644 index 0000000..6132404 --- /dev/null +++ b/MiaoUI/src/source/Render/Node/Mui_RenderNode.cpp @@ -0,0 +1,316 @@ +/** + * FileName: Mui_RenderNode.cpp + * Note: 渲染Node实现 + * + * Copyright (C) 2022-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-11-23 Create +*/ +#include + +#include + +namespace Mui::Render +{ + + MRenderNode::MRenderNode(MRenderNode* parent) + { + M_ASSERT(parent) + parent->AddChildNode(this); + } + + MRenderNode::~MRenderNode() + { + if(m_root) + m_root->UnbindNodeRenderFunc(this); + } + + MRenderNode::MRenderNode(MNodeRoot* root) + { + m_root = root; + m_root->BindNodeRenderFunc(nullptr, this); + } + + void MRenderNode::DelChildNode(std::vector::iterator& iter) + { + std::lock_guard lock(m_lock); + m_root->UnbindNodeRenderFunc(*iter); + m_nodeList.erase(iter); + } + + void MRenderNode::Visible(bool visible) + { + m_visible = visible; + + visible = m_parentVisible && visible; + std::function setchild = [&setchild, this](MRenderNode* node, bool _visible) + { + node->m_parentVisible = _visible; + + for (auto& child : node->m_nodeList) + { + setchild(child, _visible && node->m_visible); + } + }; + + for (auto& child : m_nodeList) + { + setchild(child, visible); + } + } + + bool MRenderNode::Visible() const + { + return m_visible && m_parentVisible; + } + + void MRenderNode::Name(const UIString& name) + { + m_name = name; + } + + UIString MRenderNode::Name() const + { + return m_name; + } + + void MRenderNode::AddChildNode(MRenderNode* node) + { + M_ASSERT(node) + std::lock_guard lock(m_lock); + node->m_parent = this; + node->m_render = m_render; + node->m_root = m_root; + node->m_parentVisible = Visible(); + + auto last = this; + + std::function findlast = [&last, &findlast](MRenderNode* parent) + { + last = parent; + if (!parent->m_nodeList.empty()) + findlast(parent->m_nodeList.back()); + }; + + findlast(this); + m_root->BindNodeRenderFunc(last, node); + m_nodeList.push_back(node); + } + + bool MRenderNode::DelChildNode(MRenderNode* node) + { + std::lock_guard lock(m_lock); + + std::function findparent + = [this, &findparent, &node](MRenderNode* _node) -> MRenderNode* + { + for (auto& child : _node->m_nodeList) + { + if (child == node) + return child->m_parent; + auto&& ret = findparent(child); + + if (ret == nullptr) + continue; + + return ret; + } + return nullptr; + }; + //父控件不是当前级别 查找目标父控件 + if(node->m_parent != this) + { + if (auto parent = findparent(this)) + parent->DelChildNode(node); + else + return false; + } + for (auto iter = m_nodeList.begin(); iter != m_nodeList.end(); ++iter) + { + if ((*iter) == node) + { + DelChildNode(iter); + node->m_parent = nullptr; + node->m_render = nullptr; + node->m_root = nullptr; + return true; + } + } + return false; + } + + bool MRenderNode::DelChildNode(const UIString& name) + { + std::lock_guard lock(m_lock); + for (auto iter = m_nodeList.begin(); iter != m_nodeList.end(); ++iter) + { + if ((*iter)->m_name == name) + { + DelChildNode(iter); + return true; + } + } + return false; + } + + MRenderNode* MRenderNode::FindChildNode(const UIString& name) const + { + for (auto& node : m_nodeList) + { + if (node->m_name == name) + return node; + auto&& child = node->FindChildNode(name); + + if (child == nullptr) + continue; + + return child; + } + return nullptr; + } + + void MRenderNode::Parent(MRenderNode* node) + { + if (m_parent) + m_parent->DelChildNode(node); + node->AddChildNode(this); + } + + MRenderNode* MRenderNode::Parent() const + { + return m_parent; + } + + MNodeRoot::MNodeRoot(MRenderNode* root) + { + m_render = root->m_render; + m_rootNode = root; + root->m_root = this; + } + + MNodeRoot::~MNodeRoot() = default; + + MRenderNode* MNodeRoot::RootNode() const + { + return m_rootNode; + } + + void MNodeRoot::RenderTree(void* data) + { + std::lock_guard lock(mx); + for (auto& node : m_drawList) + { + if (node.node->Visible()) + { + node.node->OnRender(m_render, data); + if (node.node->m_nodeList.empty()) + { + node.node->OnRenderChildEnd(m_render, data); + } + } + //子节点渲染结束 + auto parent = node.node->m_parent; + if (node.node != parent->m_nodeList.back()) + continue; + + //确认是最后一个子节点 + if (!node.node->m_nodeList.empty()) + continue; + + //向上遍历父级 + while (parent) + { + if (parent->Visible()) + parent->OnRenderChildEnd(m_render, data); + + if (auto pp = parent->m_parent; pp && parent != pp->m_nodeList.back()) + break; + parent = parent->m_parent; + } + } + } + + void MNodeRoot::BindNodeRenderFunc(MRenderNode* last, MRenderNode* node) + { + std::lock_guard lock(mx); + auto getDrawNode = [this](MRenderNode* _node) + { + MNodeRoot::drawNode draw; + draw.node = _node; + return draw; + }; + std::vector insertList; + //查找Node在树中的开始位置 + auto insertPos = m_drawList.end(); + if (last) + { + for (auto iter = m_drawList.begin(); iter != m_drawList.end(); ++iter) + { + if ((*iter).node == last) + { + insertPos = iter + 1; + break; + } + } + } + + //将Node的子元素放到临时列表 + std::function pushList = [&insertList, &pushList, &getDrawNode](MRenderNode* _node) + { + for (auto& child : _node->m_nodeList) + { + insertList.push_back(getDrawNode(child)); + pushList(child); + } + }; + //一并插入drawList + insertList.push_back(getDrawNode(node)); + pushList(node); + m_drawList.insert(insertPos, insertList.begin(), insertList.end()); + } + + void MNodeRoot::UnbindNodeRenderFunc(MRenderNode* node) + { + std::lock_guard lock(mx); + + auto unbindfun = [this](MRenderNode* _node, _m_size begin) + { + for (auto _it = m_drawList.begin() + (_m_long64)begin; _it != m_drawList.end(); ++_it) + { + if ((*_it).node == _node) + { + _it = m_drawList.erase(_it); + return _m_size(_it - m_drawList.begin()); + } + } + return _m_size(0); + }; + + //从当前Node位置开始往后搜索子Node + _m_size beginIndex = unbindfun(node, 0); + std::function findChild = [&unbindfun, &findChild, this](MRenderNode* _node, _m_size begin) + { + for (auto& child : _node->m_nodeList) + { + begin = unbindfun(child, begin); + if (m_callback) + m_callback(child); + findChild(child, begin); + } + }; + findChild(node, beginIndex); + } + +} diff --git a/MiaoUI/src/source/Render/Node/Mui_UINodeBase.cpp b/MiaoUI/src/source/Render/Node/Mui_UINodeBase.cpp new file mode 100644 index 0000000..e1dbfdc --- /dev/null +++ b/MiaoUI/src/source/Render/Node/Mui_UINodeBase.cpp @@ -0,0 +1,677 @@ +/** + * FileName: Mui_UINodeBase.cpp + * Note: UINodeBase实现 + * + * Copyright (C) 2022-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-11-27 Create +*/ +#include +#include +#include + +namespace Mui::Render +{ + UINodeBase::UINodeBase() + { + m_initialized = false; + } + + UINodeBase::~UINodeBase() + { + auto& list = GetNodeList(); + for (size_t i = 0; i < list.size(); i++) + { + delete list[i]; + } + list.clear(); + } + + void UINodeBase::SetName(const UIString& name) + { + Name(name); + } + + UIString UINodeBase::GetName() const + { + return Name(); + } + + void UINodeBase::SetPos(UIPoint position, bool draw) + { + m_data.Position = position; + if (draw) UpdateLayout(); + } + + void UINodeBase::SetPos(int x, int y, bool draw) + { + SetPos({ x, y }, draw); + } + + UIPoint UINodeBase::GetPos() const + { + return m_data.Position; + } + + void UINodeBase::SetPosUnit(PosSizeUnit unit, bool draw) + { + m_data.PosUnit = unit; + if (draw) UpdateLayout(); + } + + UINodeBase::PosSizeUnit UINodeBase::GetPosUnit() const + { + return m_data.PosUnit; + } + + void UINodeBase::SetSize(UISize size, bool draw) + { + m_data.Size = size; + if (draw) UpdateLayout(); + } + + void UINodeBase::SetSize(int width, int height, bool draw) + { + SetSize({ width, height }, draw); + } + + UISize UINodeBase::GetSize() const + { + return m_data.Size; + } + + void UINodeBase::SetSizeUnit(PosSizeUnit unit, bool draw) + { + m_data.SizeUnit = unit; + if (draw) UpdateLayout(); + } + + UINodeBase::PosSizeUnit UINodeBase::GetSizeUnit() const + { + return m_data.SizeUnit; + } + + void UINodeBase::SetMinSize(UISize size, bool draw) + { + m_data.MinSize = size; + if (draw) UpdateLayout(); + } + + UISize UINodeBase::GetMinSize() const + { + return m_data.MinSize; + } + + void UINodeBase::SetMaxSize(UISize size, bool draw) + { + m_data.MaxSize = size; + if (draw) UpdateLayout(); + } + + UISize UINodeBase::GetMaxSize() const + { + return m_data.MaxSize; + } + + void UINodeBase::SetPadding(UIRect padding, bool draw) + { + m_data.Padding = padding; + if (draw) UpdateLayout(); + } + + UIRect UINodeBase::GetPadding() const + { + return m_data.Padding; + } + + void UINodeBase::SetAlignType(UIAlignment type, bool draw) + { + m_data.Align.SetType(type); + + if (draw) + UpdateLayout(); + } + + UIAlignment UINodeBase::GetAlignType() const + { + return m_data.Align.GetType(); + } + + void UINodeBase::SetVisible(bool visible, bool draw) + { + Visible(visible); + + if (draw) + UpdateDisplay(); + } + + bool UINodeBase::IsVisible() const + { + return Visible(); + } + + void UINodeBase::SetAlpha(_m_byte alpha, bool draw) + { + m_data.AlphaSrc = alpha; + _m_byte parentAlpha = 255; + if(auto parent = static_cast(Parent()); parent) + { + parentAlpha = parent->m_data.AlphaDst; + } + m_data.AlphaDst = _m_color::AlphaBlend(parentAlpha, m_data.AlphaSrc); + + if (draw) + UpdateDisplay(); + } + + _m_byte UINodeBase::GetAlpha() const + { + return m_data.AlphaSrc; + } + + void UINodeBase::SetParent(UINodeBase* UINode) + { + Parent(UINode); + } + + UINodeBase* UINodeBase::GetParent() const + { + return (UINodeBase*)Parent(); + } + + void UINodeBase::AddChildren(UINodeBase* UINode) + { + AddChildNode(UINode); + UINode->m_data.ParentWnd = m_data.ParentWnd; + UINode->m_data.DPIScale = m_data.DPIScale; + UINode->m_data.AlphaDst = _m_color::AlphaBlend(m_data.AlphaDst, UINode->m_data.AlphaSrc); + UINode->InitDeviceResource(); + } + + void UINodeBase::RemoveChildren(UINodeBase* UINode) + { + DelChildNode(UINode); + UINode->m_data.ParentWnd = nullptr; + UINode->m_data.DPIScale = { 1.f, 1.f }; + } + + void UINodeBase::RemoveChildren(const UIString& name) + { + DelChildNode(name); + } + + UINodeBase* UINodeBase::FindChildren(const UIString& name) const + { + return (UINodeBase*)FindChildNode(name); + } + + _m_uint UINodeBase::GetChildrenCount() + { + _m_uint count = 0; + for (const auto node : GetNodeList()) + { + if (!static_cast(node)->m_data.HideThis) + count++; + } + return count; + } + + void UINodeBase::GetChildrenList(std::vector& list) + { + for (auto node : GetNodeList()) + { + auto _node = (UINodeBase*)node; + if (!_node->m_data.HideThis) + list.push_back(_node); + } + } + + void UINodeBase::UpdateDisplay(bool updateCache) + { + if (updateCache) + m_cacheUpdate = true; + if (m_data.ParentWnd) + { + auto t = m_data.Frame.ToRectT(); + m_data.ParentWnd->UpdateDisplay(&t); + } + } + + void UINodeBase::UpdateLayout() + { + if (!m_render) + return; + + m_render->RunTask([this] + { + const auto parent = GetValidParent(); + if (!parent) + { + if (dynamic_cast(m_data.ParentWnd->GetRootControl()) == this) + m_data.ParentWnd->UpdateLayout(nullptr); + return; + } + if (parent->m_data.AutoSize) + { + parent->UpdateLayout(); + UpdateDisplay(); + return; + } + //线性布局将影响下一个Node + switch (parent->m_data.Align.GetType()) + { + case UIAlignment_Block: + case UIAlignment_LinearV: + case UIAlignment_LinearVB: + case UIAlignment_LinearVR: + case UIAlignment_LinearVBR: + case UIAlignment_LinearH: + case UIAlignment_LinearHB: + case UIAlignment_LinearHL: + case UIAlignment_LinearHLB: + { + //查找当前Node在父Node中的Index + auto& list = parent->GetNodeList(); + const auto thisIter = std::find(list.begin(), list.end(), (MRenderNode*)this); + const auto thisIndex = (size_t)std::distance(list.begin(), thisIter); + //从当前node往后刷新 + parent->m_data.Align.Layout(parent, thisIndex, false); + } + break; + //直接计算 + case UIAlignment_Absolute: + { + auto [box, clip] = m_data.Align.CalcPadding(parent); + _m_pointf pt; + _m_rectf& frame = m_data.Align.CalcBaseFrame(this, box, pt, { box.left, box.top }, false); + const _m_sizef _size = m_data.Align.CalcContentSize(this, false); + frame.right = frame.left + _size.width; + frame.bottom = frame.top + _size.height; + + m_data.Align.Intersect(&m_data.ClipFrame, &box, &m_data.Frame); + + m_data.Align.Layout(this, 0, false); + + OnLayoutCalced(); + } + break; + case UIAlignment_Center: + { + auto [box, clip] = m_data.Align.CalcPadding(parent); + const _m_sizef boxSize = { box.GetWidth(), box.GetHeight() }; + _m_pointf pt; + _m_rectf& frame = m_data.Align.CalcBaseFrame(this, box, pt, { box.left, box.top }, false); + const _m_sizef _size = m_data.Align.CalcContentSize(this, false); + frame.left = box.left + (boxSize.width - _size.width) / 2.f + pt.x; + frame.top = box.top + (boxSize.height - _size.height) / 2.f + pt.y; + frame.right = frame.left + _size.width; + frame.bottom = frame.top + _size.height; + + m_data.Align.Intersect(&m_data.ClipFrame, &box, &m_data.Frame); + + m_data.Align.Layout(this, 0, false); + + OnLayoutCalced(); + } + break; + default: + m_data.Align.Layout(this); + break; + } + + m_cacheUpdate = true; + UpdateDisplay(); + }); + } + + void UINodeBase::SetCacheType(UICacheType type) + { + m_data.CacheType = type; + UpdateDisplay(); + } + + UICacheType UINodeBase::GetCacheType() const + { + return m_data.CacheType; + } + + void UINodeBase::SetAnimationState(bool begin) + { + + } + + void UINodeBase::SetBackground(UIBkgndStyle style) + { + if (style.bkgndColor == 0 && style.ShadowColor == 0) + m_brush.BkgndBrush = nullptr; + if (style.FrameWidth == 0) + m_brush.FramePen = nullptr; + + if (memcmp(&m_bgStyle, &style, sizeof UIBkgndStyle) != 0) + { + if (style.bkgndColor != 0 || style.ShadowColor != 0) + { + if (!m_brush.BkgndBrush) + m_brush.BkgndBrush = m_render->CreateBrush(style.bkgndColor); + else + m_brush.BkgndBrush->SetColor(style.bkgndColor); + } + if (style.FrameWidth != 0) + { + if (!m_brush.FramePen) + m_brush.FramePen = m_render->CreatePen(style.FrameWidth, style.FrameColor); + else + m_brush.FramePen->SetWidthAndColor(style.FrameWidth, style.FrameColor); + } + m_bgStyle = style; + m_cacheUpdate = true; + UpdateDisplay(); + } + } + + UIBkgndStyle UINodeBase::GetBkgndStyle() const + { + return m_bgStyle; + } + + void UINodeBase::SetScale(_m_rcscale scale, bool draw) + { + m_data.RectScale = scale; + UpdateScale(); + if (draw) + UpdateLayout(); + } + + _m_rcscale UINodeBase::GetScale() const + { + return m_data.RectScale; + } + + void UINodeBase::EnableDPIScale(bool enable) + { + m_data.EnableDPIScale = enable; + UpdateScale(); + } + + bool UINodeBase::IsDPIScaleEnabled() const + { + return m_data.EnableDPIScale; + } + + void UINodeBase::Frame(_m_rect frame) + { + if (m_data.HideThis) + m_data.Frame = frame.ToRectT(); + } + + _m_rect UINodeBase::Frame() const + { + return m_data.Frame.ToRectT(); + } + + void UINodeBase::OnLoadResource(MRenderCmd* render, bool recreate) + { + m_brush.BkgndBrush = nullptr; + m_brush.FramePen = nullptr; + + if (m_bgStyle.bkgndColor != 0 || m_bgStyle.ShadowColor != 0) + { + if (!m_brush.BkgndBrush) + m_brush.BkgndBrush = m_render->CreateBrush(m_bgStyle.bkgndColor); + else + m_brush.BkgndBrush->SetColor(m_bgStyle.bkgndColor); + } + if (m_bgStyle.FrameWidth != 0) + { + if (!m_brush.FramePen) + m_brush.FramePen = m_render->CreatePen(m_bgStyle.FrameWidth, m_bgStyle.FrameColor); + else + m_brush.FramePen->SetWidthAndColor(m_bgStyle.FrameWidth, m_bgStyle.FrameColor); + } + } + + UINodeBase* UINodeBase::GetValidParent() const + { + auto parent_ = Parent(); + if (!parent_) return nullptr; + + std::function parent + = [&parent](MRenderNode* node) -> UINodeBase* + { + if (const auto _cast = (UINodeBase*)node) + return _cast; + + const auto _parent = static_cast(node)->Parent(); + if (!_parent) + return nullptr; + + return parent(_parent); + }; + return parent(parent_); + } + + void UINodeBase::UpdateScale() + { + if (m_data.ParentWnd) + OnScale(m_data.ParentWnd->GetWindowScale()); + else + OnScale({ 1.f, 1.f }); + m_cacheUpdate = true; + } + + void UINodeBase::OnScale(_m_scale scale) + { + m_data.DPIScale = scale; + for (const auto& node : GetNodeList()) + { + if (auto cast = (UINodeBase*)node) + cast->OnScale(scale); + } + } + + _m_sizef UINodeBase::GetContentSize() + { + auto getChildSize = [this](auto src) -> _m_rectf + { + if (src->GetNodeList().empty()) + return { 0, 0, 0, 0 }; + + float minX = m_data.Frame.left, minY = m_data.Frame.top, maxR = (float)INT_MIN, maxB = (float)INT_MIN; + for (const auto& node : src->GetNodeList()) + { + const auto _node = (UINodeBase*)node; + if (!_node || _node->m_data.HideThis) continue; + + const _m_rectf& frame = _node->m_data.Frame; + + if (frame.left < minX) + minX = frame.left; + if (frame.top < minY) + minY = frame.top; + if (frame.right > maxR) + maxR = frame.right; + if (frame.bottom > maxB) + maxB = frame.bottom; + } + return { minX, minY, maxR, maxB }; + }; + + _m_sizef retSize; + + //自动尺寸 + if(m_data.AutoSize || (m_data.SizeUnit.x_w == Percentage || m_data.SizeUnit.y_h == Percentage) + || (m_data.SizeUnit.x_w == FillMinus || m_data.SizeUnit.y_h == FillMinus)) + { + const auto parent = GetValidParent(); + if (!parent && (m_data.SizeUnit.x_w == Percentage|| m_data.SizeUnit.y_h == Percentage + || m_data.SizeUnit.x_w == FillMinus || m_data.SizeUnit.y_h == FillMinus)) + { + retSize = { 0, 0 }; + } + else + { + const auto&& [xs, ys, ws, hs] = GetRectScale(); + const auto&& rect = getChildSize(this); + retSize = { rect.right - rect.left, rect.bottom - rect.top }; + retSize.width += _scale_to(float(m_data.Padding.left + m_data.Padding.right), ws); + retSize.height += _scale_to(float(m_data.Padding.top + m_data.Padding.bottom), hs); + } + } + if (!m_data.AutoSize) + { + //绝对尺寸 + const auto&& [xs, ys, ws, hs] = GetRectScale(); + if (m_data.SizeUnit.x_w == Pixel) + retSize.width = _scale_to((float)m_data.Size.width, ws); + if (m_data.SizeUnit.y_h == Pixel) + retSize.height = _scale_to((float)m_data.Size.height, hs); + } + return retSize; + } + + _m_pointf UINodeBase::GetCalcedPoint() + { + _m_pointf pt; + /*if(m_data.PosUint == Pixel) + {*/ + auto [xs, ys, ws, hs] = GetRectScale(); + pt.x = _scale_to((float)m_data.Position.x, xs); + pt.y = _scale_to((float)m_data.Position.y, ys); + //} + return pt; + } + + void UINodeBase::OnRender(MRenderCmd* render, void* data) + { + bool isCache = false; + + _m_rect destRect = m_data.Frame.ToRectT(); + + //计算Alpha + _m_byte parentAlpha = static_cast(Parent())->m_data.AlphaDst; + m_data.AlphaDst = _m_color::AlphaBlend(parentAlpha, m_data.AlphaSrc); + + render->PushClipRect(m_data.ClipFrame.ToRectT()); + + PaintBackground(render, &destRect, isCache); + + const auto clipRect = m_data.ClipFrame.ToRectT(); + const MPaintParam paintpm = { render, &clipRect, &destRect, isCache, m_data.AlphaDst }; + OnPaintProc(&paintpm); + + render->PopClipRect(); + } + + void UINodeBase::OnRenderChildEnd(MRenderCmd* render, void* data) + { + if (!m_data.ParentWnd->m_dbgFrame && !m_brush.FramePen) + return; + + _m_rect dst = m_data.Frame.ToRectT(); + + render->ResetCanvas(); + render->PushClipRect(m_data.ClipFrame.ToRectT()); + + if (m_brush.FramePen && m_bgStyle.FrameColor) + { + _m_byte&& parentAlpha = m_brush.FramePen->GetOpacity(); + auto scale = GetRectScale().scale(); + auto _scale = Helper::M_MIN(scale.cx, scale.cy); + m_brush.FramePen->SetOpacity((_m_byte)((float)m_data.AlphaDst * (float)parentAlpha / 255.f)); + m_brush.FramePen->SetWidth(_scale_to(m_bgStyle.FrameWidth, _scale)); + if (m_bgStyle.RoundValue != 0.f) + { + const float clipValue = _scale * m_bgStyle.RoundValue; + render->DrawRoundedRect(dst, clipValue, m_brush.FramePen); + } + else + render->DrawRectangle(dst, m_brush.FramePen); + + m_brush.FramePen->SetOpacity(parentAlpha); + } + + //调试矩形 + if (m_data.ParentWnd->m_dbgFrame) + { + if (m_data.ParentWnd->m_highlight && m_data.ParentWnd->m_focus.curFocus == this) + m_data.ParentWnd->m_dbgFrame->SetColor(Color::M_BLUE); + else + m_data.ParentWnd->m_dbgFrame->SetColor(Color::M_RED); + render->DrawRectangle(dst, m_data.ParentWnd->m_dbgFrame); + } + render->PopClipRect(); + } + + void UINodeBase::PaintBackground(MRenderCmd* render, MPCRect dst, bool cache) + { + if (m_bgStyle.bkgndColor && m_brush.BkgndBrush) + { + //保存Alpha + _m_byte&& parentAlpha = m_brush.BkgndBrush->GetOpacity(); + //混合Alpha + if (!cache) + m_brush.BkgndBrush->SetOpacity(_m_byte((float)m_data.AlphaDst * (float)parentAlpha / 255.f)); + + //绘制纯色背景 + if (m_bgStyle.RoundValue != 0.f) + { + auto scale = GetRectScale().scale(); + const float clipValue = Helper::M_MIN(scale.cx, scale.cy) * m_bgStyle.RoundValue; + render->FillRoundedRect(*dst, clipValue, m_brush.BkgndBrush); + } + else + render->FillRectangle(*dst, m_brush.BkgndBrush); + + //还原Alpha + if (!cache) + m_brush.BkgndBrush->SetOpacity(parentAlpha); + } + } + + void UINodeBase::HideNode(bool hide, bool draw) + { + m_data.HideThis = hide; + if (draw) + { + if (const auto parent = GetValidParent(); parent) + parent->UpdateLayout(); + } + } + + bool UINodeBase::HideNode() const + { + return m_data.HideThis; + } + + void UINodeBase::AutoSize(bool autosize, bool draw) + { + m_data.AutoSize = autosize; + if (draw) + UpdateLayout(); + } + + bool UINodeBase::AutoSize() const + { + return m_data.AutoSize; + } + + void UINodeBase::InitDeviceResource() + { + if (m_initialized) + return; + OnLoadResource(m_render, false); + m_initialized = true; + } +} diff --git a/MiaoUI/src/source/ThirdParty/aes256.cpp b/MiaoUI/src/source/ThirdParty/aes256.cpp new file mode 100644 index 0000000..4446e26 --- /dev/null +++ b/MiaoUI/src/source/ThirdParty/aes256.cpp @@ -0,0 +1,266 @@ +#include "aes256.h" + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 +#define Nk 8 +#define Nr 14 +#define AES_BLOCKLEN 16 + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// state - array holding the intermediate results during decryption. +typedef uint8_t state_t[4][4]; + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +const uint8_t sbox[256] = +{ + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 +}; + +// The round constant word array, Rcon[i], contains the values given by +// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +static const uint8_t Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + + // This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) +{ + unsigned i, j, k; + uint8_t tempa[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for (i = Nk; i < Nb * (Nr + 1); ++i) + { + { + k = (i - 1) * 4; + tempa[0] = RoundKey[k + 0]; + tempa[1] = RoundKey[k + 1]; + tempa[2] = RoundKey[k + 2]; + tempa[3] = RoundKey[k + 3]; + + } + + if (i % Nk == 0) + { + // This function shifts the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + const uint8_t u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = sbox[tempa[0]]; + tempa[1] = sbox[tempa[1]]; + tempa[2] = sbox[tempa[2]]; + tempa[3] = sbox[tempa[3]]; + } + + tempa[0] = tempa[0] ^ Rcon[i / Nk]; + } + if (i % Nk == 4) + { + // Function Subword() + { + tempa[0] = sbox[tempa[0]]; + tempa[1] = sbox[tempa[1]]; + tempa[2] = sbox[tempa[2]]; + tempa[3] = sbox[tempa[3]]; + } + } + j = i * 4; k = (i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void SubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = sbox[(*state)[j][i]]; + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +static void ShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to left + temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + // Rotate second row 2 columns to left + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to left + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +inline uint8_t xtime(uint8_t x) +{ + return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); +} + +//// MixColumns function mixes the columns of the state matrix +static void MixColumns(state_t* state) +{ + uint8_t i; + uint8_t Tmp, Tm, t; + for (i = 0; i < 4; ++i) + { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3]; + Tm = (*state)[i][0] ^ (*state)[i][1]; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp; + Tm = (*state)[i][1] ^ (*state)[i][2]; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp; + Tm = (*state)[i][2] ^ (*state)[i][3]; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp; + Tm = (*state)[i][3] ^ t; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp; + } +} + + +// Cipher is the main function that encrypts the PlainText. +static void Cipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(0, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without MixColumns() + for (round = 1; ; ++round) + { + SubBytes(state); + ShiftRows(state); + if (round == Nr) { + break; + } + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + // Add round key to last round + AddRoundKey(Nr, state, RoundKey); +} + +void AES256_CFB(UIResource src, _m_byte* out, _m_byte* key, _m_byte* iv, bool encrypt) +{ + uint8_t RoundKey[240] = { 0 }; + uint8_t buffer[AES_BLOCKLEN] = { 0 }; + + int blocksize = AES_BLOCKLEN; + int lastblock = src.size / AES_BLOCKLEN; + if (src.size % AES_BLOCKLEN == 0) lastblock--; + + KeyExpansion(RoundKey, key); + memcpy(buffer, iv, AES_BLOCKLEN); + + if (src.data != out) + memcpy(out, src.data, src.size); + + int i; + for (i = 0; i <= lastblock; ++i) + { + // encrypt iv value + Cipher((state_t*)buffer, RoundKey); + + // last block could be smaller because of no padding + if (i == lastblock) + blocksize = src.size - (i * AES_BLOCKLEN); + + // XOR input with encrypted counter + for (int ix = 0; ix < blocksize; ++ix) + out[ix] ^= buffer[ix]; + + // crypted input block will be used for next iteration + if (!encrypt) + memcpy(buffer, src.data, AES_BLOCKLEN); + else + memcpy(buffer, out, AES_BLOCKLEN); + + // move data buffer pointer 1 block further + src.data += AES_BLOCKLEN; + out += AES_BLOCKLEN; + } +} diff --git a/MiaoUI/src/source/ThirdParty/aes256.h b/MiaoUI/src/source/ThirdParty/aes256.h new file mode 100644 index 0000000..2174807 --- /dev/null +++ b/MiaoUI/src/source/ThirdParty/aes256.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +using namespace Mui; + +//aes256 cfb模式的软件实现 +void AES256_CFB(UIResource src, _m_byte* out, _m_byte* key, _m_byte* iv, bool encrypt); \ No newline at end of file diff --git a/MiaoUI/src/source/ThirdParty/licenseType.txt b/MiaoUI/src/source/ThirdParty/licenseType.txt new file mode 100644 index 0000000..62b5c6c --- /dev/null +++ b/MiaoUI/src/source/ThirdParty/licenseType.txt @@ -0,0 +1,2 @@ +picosha2 - MIT +pugixml - MIT \ No newline at end of file diff --git a/MiaoUI/src/source/ThirdParty/picosha2.h b/MiaoUI/src/source/ThirdParty/picosha2.h new file mode 100644 index 0000000..d73072b --- /dev/null +++ b/MiaoUI/src/source/ThirdParty/picosha2.h @@ -0,0 +1,377 @@ +/* +The MIT License (MIT) + +Copyright (C) 2017 okdshin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#ifndef PICOSHA2_H +#define PICOSHA2_H +// picosha2:20140213 + +#ifndef PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR +#define PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR \ + 1048576 //=1024*1024: default is 1MB memory +#endif + +#include +#include +#include +#include +#include +#include +namespace picosha2 { +typedef unsigned long word_t; +typedef unsigned char byte_t; + +static const size_t k_digest_size = 32; + +namespace detail { +inline byte_t mask_8bit(byte_t x) { return x & 0xff; } + +inline word_t mask_32bit(word_t x) { return x & 0xffffffff; } + +const word_t add_constant[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + +const word_t initial_message_digest[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, + 0xa54ff53a, 0x510e527f, 0x9b05688c, + 0x1f83d9ab, 0x5be0cd19}; + +inline word_t ch(word_t x, word_t y, word_t z) { return (x & y) ^ ((~x) & z); } + +inline word_t maj(word_t x, word_t y, word_t z) { + return (x & y) ^ (x & z) ^ (y & z); +} + +inline word_t rotr(word_t x, std::size_t n) { + assert(n < 32); + return mask_32bit((x >> n) | (x << (32 - n))); +} + +inline word_t bsig0(word_t x) { return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); } + +inline word_t bsig1(word_t x) { return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); } + +inline word_t shr(word_t x, std::size_t n) { + assert(n < 32); + return x >> n; +} + +inline word_t ssig0(word_t x) { return rotr(x, 7) ^ rotr(x, 18) ^ shr(x, 3); } + +inline word_t ssig1(word_t x) { return rotr(x, 17) ^ rotr(x, 19) ^ shr(x, 10); } + +template +void hash256_block(RaIter1 message_digest, RaIter2 first, RaIter2 last) { + assert(first + 64 == last); + static_cast(last); // for avoiding unused-variable warning + word_t w[64]; + std::fill(w, w + 64, word_t(0)); + for (std::size_t i = 0; i < 16; ++i) { + w[i] = (static_cast(mask_8bit(*(first + i * 4))) << 24) | + (static_cast(mask_8bit(*(first + i * 4 + 1))) << 16) | + (static_cast(mask_8bit(*(first + i * 4 + 2))) << 8) | + (static_cast(mask_8bit(*(first + i * 4 + 3)))); + } + for (std::size_t i = 16; i < 64; ++i) { + w[i] = mask_32bit(ssig1(w[i - 2]) + w[i - 7] + ssig0(w[i - 15]) + + w[i - 16]); + } + + word_t a = *message_digest; + word_t b = *(message_digest + 1); + word_t c = *(message_digest + 2); + word_t d = *(message_digest + 3); + word_t e = *(message_digest + 4); + word_t f = *(message_digest + 5); + word_t g = *(message_digest + 6); + word_t h = *(message_digest + 7); + + for (std::size_t i = 0; i < 64; ++i) { + word_t temp1 = h + bsig1(e) + ch(e, f, g) + add_constant[i] + w[i]; + word_t temp2 = bsig0(a) + maj(a, b, c); + h = g; + g = f; + f = e; + e = mask_32bit(d + temp1); + d = c; + c = b; + b = a; + a = mask_32bit(temp1 + temp2); + } + *message_digest += a; + *(message_digest + 1) += b; + *(message_digest + 2) += c; + *(message_digest + 3) += d; + *(message_digest + 4) += e; + *(message_digest + 5) += f; + *(message_digest + 6) += g; + *(message_digest + 7) += h; + for (std::size_t i = 0; i < 8; ++i) { + *(message_digest + i) = mask_32bit(*(message_digest + i)); + } +} + +} // namespace detail + +template +void output_hex(InIter first, InIter last, std::ostream& os) { + os.setf(std::ios::hex, std::ios::basefield); + while (first != last) { + os.width(2); + os.fill('0'); + os << static_cast(*first); + ++first; + } + os.setf(std::ios::dec, std::ios::basefield); +} + +template +void bytes_to_hex_string(InIter first, InIter last, std::string& hex_str) { + std::ostringstream oss; + output_hex(first, last, oss); + hex_str.assign(oss.str()); +} + +template +void bytes_to_hex_string(const InContainer& bytes, std::string& hex_str) { + bytes_to_hex_string(bytes.begin(), bytes.end(), hex_str); +} + +template +std::string bytes_to_hex_string(InIter first, InIter last) { + std::string hex_str; + bytes_to_hex_string(first, last, hex_str); + return hex_str; +} + +template +std::string bytes_to_hex_string(const InContainer& bytes) { + std::string hex_str; + bytes_to_hex_string(bytes, hex_str); + return hex_str; +} + +class hash256_one_by_one { + public: + hash256_one_by_one() { init(); } + + void init() { + buffer_.clear(); + std::fill(data_length_digits_, data_length_digits_ + 4, word_t(0)); + std::copy(detail::initial_message_digest, + detail::initial_message_digest + 8, h_); + } + + template + void process(RaIter first, RaIter last) { + add_to_data_length(static_cast(std::distance(first, last))); + std::copy(first, last, std::back_inserter(buffer_)); + std::size_t i = 0; + for (; i + 64 <= buffer_.size(); i += 64) { + detail::hash256_block(h_, buffer_.begin() + i, + buffer_.begin() + i + 64); + } + buffer_.erase(buffer_.begin(), buffer_.begin() + i); + } + + void finish() { + byte_t temp[64]; + std::fill(temp, temp + 64, byte_t(0)); + std::size_t remains = buffer_.size(); + std::copy(buffer_.begin(), buffer_.end(), temp); + temp[remains] = 0x80; + + if (remains > 55) { + std::fill(temp + remains + 1, temp + 64, byte_t(0)); + detail::hash256_block(h_, temp, temp + 64); + std::fill(temp, temp + 64 - 4, byte_t(0)); + } else { + std::fill(temp + remains + 1, temp + 64 - 4, byte_t(0)); + } + + write_data_bit_length(&(temp[56])); + detail::hash256_block(h_, temp, temp + 64); + } + + template + void get_hash_bytes(OutIter first, OutIter last) const { + for (const word_t* iter = h_; iter != h_ + 8; ++iter) { + for (std::size_t i = 0; i < 4 && first != last; ++i) { + *(first++) = detail::mask_8bit( + static_cast((*iter >> (24 - 8 * i)))); + } + } + } + + private: + void add_to_data_length(word_t n) { + word_t carry = 0; + data_length_digits_[0] += n; + for (std::size_t i = 0; i < 4; ++i) { + data_length_digits_[i] += carry; + if (data_length_digits_[i] >= 65536u) { + carry = data_length_digits_[i] >> 16; + data_length_digits_[i] &= 65535u; + } else { + break; + } + } + } + void write_data_bit_length(byte_t* begin) { + word_t data_bit_length_digits[4]; + std::copy(data_length_digits_, data_length_digits_ + 4, + data_bit_length_digits); + + // convert byte length to bit length (multiply 8 or shift 3 times left) + word_t carry = 0; + for (std::size_t i = 0; i < 4; ++i) { + word_t before_val = data_bit_length_digits[i]; + data_bit_length_digits[i] <<= 3; + data_bit_length_digits[i] |= carry; + data_bit_length_digits[i] &= 65535u; + carry = (before_val >> (16 - 3)) & 65535u; + } + + // write data_bit_length + for (int i = 3; i >= 0; --i) { + (*begin++) = static_cast(data_bit_length_digits[i] >> 8); + (*begin++) = static_cast(data_bit_length_digits[i]); + } + } + std::vector buffer_; + word_t data_length_digits_[4]; // as 64bit integer (16bit x 4 integer) + word_t h_[8]; +}; + +inline void get_hash_hex_string(const hash256_one_by_one& hasher, + std::string& hex_str) { + byte_t hash[k_digest_size]; + hasher.get_hash_bytes(hash, hash + k_digest_size); + return bytes_to_hex_string(hash, hash + k_digest_size, hex_str); +} + +inline std::string get_hash_hex_string(const hash256_one_by_one& hasher) { + std::string hex_str; + get_hash_hex_string(hasher, hex_str); + return hex_str; +} + +namespace impl { +template +void hash256_impl(RaIter first, RaIter last, OutIter first2, OutIter last2, int, + std::random_access_iterator_tag) { + hash256_one_by_one hasher; + // hasher.init(); + hasher.process(first, last); + hasher.finish(); + hasher.get_hash_bytes(first2, last2); +} + +template +void hash256_impl(InputIter first, InputIter last, OutIter first2, + OutIter last2, int buffer_size, std::input_iterator_tag) { + std::vector buffer(buffer_size); + hash256_one_by_one hasher; + // hasher.init(); + while (first != last) { + int size = buffer_size; + for (int i = 0; i != buffer_size; ++i, ++first) { + if (first == last) { + size = i; + break; + } + buffer[i] = *first; + } + hasher.process(buffer.begin(), buffer.begin() + size); + } + hasher.finish(); + hasher.get_hash_bytes(first2, last2); +} +} + +template +void hash256(InIter first, InIter last, OutIter first2, OutIter last2, + int buffer_size = PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR) { + picosha2::impl::hash256_impl( + first, last, first2, last2, buffer_size, + typename std::iterator_traits::iterator_category()); +} + +template +void hash256(InIter first, InIter last, OutContainer& dst) { + hash256(first, last, dst.begin(), dst.end()); +} + +template +void hash256(const InContainer& src, OutIter first, OutIter last) { + hash256(src.begin(), src.end(), first, last); +} + +template +void hash256(const InContainer& src, OutContainer& dst) { + hash256(src.begin(), src.end(), dst.begin(), dst.end()); +} + +template +void hash256_hex_string(InIter first, InIter last, std::string& hex_str) { + byte_t hashed[k_digest_size]; + hash256(first, last, hashed, hashed + k_digest_size); + std::ostringstream oss; + output_hex(hashed, hashed + k_digest_size, oss); + hex_str.assign(oss.str()); +} + +template +std::string hash256_hex_string(InIter first, InIter last) { + std::string hex_str; + hash256_hex_string(first, last, hex_str); + return hex_str; +} + +inline void hash256_hex_string(const std::string& src, std::string& hex_str) { + hash256_hex_string(src.begin(), src.end(), hex_str); +} + +template +void hash256_hex_string(const InContainer& src, std::string& hex_str) { + hash256_hex_string(src.begin(), src.end(), hex_str); +} + +template +std::string hash256_hex_string(const InContainer& src) { + return hash256_hex_string(src.begin(), src.end()); +} +templatevoid hash256(std::ifstream& f, OutIter first, OutIter last){ + hash256(std::istreambuf_iterator(f), std::istreambuf_iterator(), first,last); + +} +}// namespace picosha2 +#endif // PICOSHA2_H \ No newline at end of file diff --git a/MiaoUI/src/source/ThirdParty/pugixml/pugiconfig.hpp b/MiaoUI/src/source/ThirdParty/pugixml/pugiconfig.hpp new file mode 100644 index 0000000..c80a365 --- /dev/null +++ b/MiaoUI/src/source/ThirdParty/pugixml/pugiconfig.hpp @@ -0,0 +1,77 @@ +/** + * pugixml parser - version 1.12 + * -------------------------------------------------------- + * Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at https://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef HEADER_PUGICONFIG_HPP +#define HEADER_PUGICONFIG_HPP + +// Uncomment this to enable wchar_t mode +#define PUGIXML_WCHAR_MODE + +// Uncomment this to enable compact mode +// #define PUGIXML_COMPACT + +// Uncomment this to disable XPath +#define PUGIXML_NO_XPATH + +// Uncomment this to disable STL +// #define PUGIXML_NO_STL + +// Uncomment this to disable exceptions +// #define PUGIXML_NO_EXCEPTIONS + +// Set this to control attributes for public classes/functions, i.e.: +// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL +// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL +// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall +// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead + +// Tune these constants to adjust memory-related behavior +// #define PUGIXML_MEMORY_PAGE_SIZE 32768 +// #define PUGIXML_MEMORY_OUTPUT_STACK 10240 +// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096 + +// Tune this constant to adjust max nesting for XPath queries +// #define PUGIXML_XPATH_DEPTH_LIMIT 1024 + +// Uncomment this to switch to header-only version +// #define PUGIXML_HEADER_ONLY + +// Uncomment this to enable long long support +// #define PUGIXML_HAS_LONG_LONG + +#endif + +/** + * Copyright (c) 2006-2022 Arseny Kapoulkine + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ diff --git a/MiaoUI/src/source/ThirdParty/pugixml/pugixml.cpp b/MiaoUI/src/source/ThirdParty/pugixml/pugixml.cpp new file mode 100644 index 0000000..60b55da --- /dev/null +++ b/MiaoUI/src/source/ThirdParty/pugixml/pugixml.cpp @@ -0,0 +1,13029 @@ +/** + * pugixml parser - version 1.12 + * -------------------------------------------------------- + * Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at https://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef SOURCE_PUGIXML_CPP +#define SOURCE_PUGIXML_CPP + +#include "pugixml.hpp" + +#include +#include +#include +#include +#include + +#ifdef PUGIXML_WCHAR_MODE +# include +#endif + +#ifndef PUGIXML_NO_XPATH +# include +# include +#endif + +#ifndef PUGIXML_NO_STL +# include +# include +# include +#endif + +// For placement new +#include + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4324) // structure was padded due to __declspec(align()) +# pragma warning(disable: 4702) // unreachable code +# pragma warning(disable: 4996) // this function or variable may be unsafe +#endif + +#if defined(_MSC_VER) && defined(__c2__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated" // this function or variable may be unsafe +#endif + +#ifdef __INTEL_COMPILER +# pragma warning(disable: 177) // function was declared but never referenced +# pragma warning(disable: 279) // controlling expression is constant +# pragma warning(disable: 1478 1786) // function was declared "deprecated" +# pragma warning(disable: 1684) // conversion from pointer to same-sized integral type +#endif + +#if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY) +# pragma warn -8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away +#endif + +#ifdef __BORLANDC__ +# pragma option push +# pragma warn -8008 // condition is always false +# pragma warn -8066 // unreachable code +#endif + +#ifdef __SNC__ +// Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug +# pragma diag_suppress=178 // function was declared but never referenced +# pragma diag_suppress=237 // controlling expression is constant +#endif + +#ifdef __TI_COMPILER_VERSION__ +# pragma diag_suppress 179 // function was declared but never referenced +#endif + +// Inlining controls +#if defined(_MSC_VER) && _MSC_VER >= 1300 +# define PUGI__NO_INLINE __declspec(noinline) +#elif defined(__GNUC__) +# define PUGI__NO_INLINE __attribute__((noinline)) +#else +# define PUGI__NO_INLINE +#endif + +// Branch weight controls +#if defined(__GNUC__) && !defined(__c2__) +# define PUGI__UNLIKELY(cond) __builtin_expect(cond, 0) +#else +# define PUGI__UNLIKELY(cond) (cond) +#endif + +// Simple static assertion +#define PUGI__STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } + +// Digital Mars C++ bug workaround for passing char loaded from memory via stack +#ifdef __DMC__ +# define PUGI__DMC_VOLATILE volatile +#else +# define PUGI__DMC_VOLATILE +#endif + +// Integer sanitizer workaround; we only apply this for clang since gcc8 has no_sanitize but not unsigned-integer-overflow and produces "attribute directive ignored" warnings +#if defined(__clang__) && defined(__has_attribute) +# if __has_attribute(no_sanitize) +# define PUGI__UNSIGNED_OVERFLOW __attribute__((no_sanitize("unsigned-integer-overflow"))) +# else +# define PUGI__UNSIGNED_OVERFLOW +# endif +#else +# define PUGI__UNSIGNED_OVERFLOW +#endif + +// Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all) +#if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST) +using std::memcpy; +using std::memmove; +using std::memset; +#endif + +// Some MinGW/GCC versions have headers that erroneously omit LLONG_MIN/LLONG_MAX/ULLONG_MAX definitions from limits.h in some configurations +#if defined(PUGIXML_HAS_LONG_LONG) && defined(__GNUC__) && !defined(LLONG_MAX) && !defined(LLONG_MIN) && !defined(ULLONG_MAX) +# define LLONG_MIN (-LLONG_MAX - 1LL) +# define LLONG_MAX __LONG_LONG_MAX__ +# define ULLONG_MAX (LLONG_MAX * 2ULL + 1ULL) +#endif + +// In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features +#if defined(_MSC_VER) && !defined(__S3E__) && !defined(_WIN32_WCE) +# define PUGI__MSVC_CRT_VERSION _MSC_VER +#elif defined(_WIN32_WCE) +# define PUGI__MSVC_CRT_VERSION 1310 // MSVC7.1 +#endif + +// Not all platforms have snprintf; we define a wrapper that uses snprintf if possible. This only works with buffers with a known size. +#if __cplusplus >= 201103 +# define PUGI__SNPRINTF(buf, ...) snprintf(buf, sizeof(buf), __VA_ARGS__) +#elif defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 +# define PUGI__SNPRINTF(buf, ...) _snprintf_s(buf, _countof(buf), _TRUNCATE, __VA_ARGS__) +#else +# define PUGI__SNPRINTF sprintf +#endif + +// We put implementation details into an anonymous namespace in source mode, but have to keep it in non-anonymous namespace in header-only mode to prevent binary bloat. +#ifdef PUGIXML_HEADER_ONLY +# define PUGI__NS_BEGIN namespace pugi { namespace impl { +# define PUGI__NS_END } } +# define PUGI__FN inline +# define PUGI__FN_NO_INLINE inline +#else +# if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces +# define PUGI__NS_BEGIN namespace pugi { namespace impl { +# define PUGI__NS_END } } +# else +# define PUGI__NS_BEGIN namespace pugi { namespace impl { namespace { +# define PUGI__NS_END } } } +# endif +# define PUGI__FN +# define PUGI__FN_NO_INLINE PUGI__NO_INLINE +#endif + +// uintptr_t +#if (defined(_MSC_VER) && _MSC_VER < 1600) || (defined(__BORLANDC__) && __BORLANDC__ < 0x561) +namespace pugi +{ +# ifndef _UINTPTR_T_DEFINED + typedef size_t uintptr_t; +# endif + + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +} +#else +# include +#endif + +// Memory allocation +PUGI__NS_BEGIN + PUGI__FN void* default_allocate(size_t size) + { + return malloc(size); + } + + PUGI__FN void default_deallocate(void* ptr) + { + free(ptr); + } + + template + struct xml_memory_management_function_storage + { + static allocation_function allocate; + static deallocation_function deallocate; + }; + + // Global allocation functions are stored in class statics so that in header mode linker deduplicates them + // Without a template<> we'll get multiple definitions of the same static + template allocation_function xml_memory_management_function_storage::allocate = default_allocate; + template deallocation_function xml_memory_management_function_storage::deallocate = default_deallocate; + + typedef xml_memory_management_function_storage xml_memory; +PUGI__NS_END + +// String utilities +PUGI__NS_BEGIN + // Get string length + PUGI__FN size_t strlength(const char_t* s) + { + assert(s); + + #ifdef PUGIXML_WCHAR_MODE + return wcslen(s); + #else + return strlen(s); + #endif + } + + // Compare two strings + PUGI__FN bool strequal(const char_t* src, const char_t* dst) + { + assert(src && dst); + + #ifdef PUGIXML_WCHAR_MODE + return wcscmp(src, dst) == 0; + #else + return strcmp(src, dst) == 0; + #endif + } + + // Compare lhs with [rhs_begin, rhs_end) + PUGI__FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) + { + for (size_t i = 0; i < count; ++i) + if (lhs[i] != rhs[i]) + return false; + + return lhs[count] == 0; + } + + // Get length of wide string, even if CRT lacks wide character support + PUGI__FN size_t strlength_wide(const wchar_t* s) + { + assert(s); + + #ifdef PUGIXML_WCHAR_MODE + return wcslen(s); + #else + const wchar_t* end = s; + while (*end) end++; + return static_cast(end - s); + #endif + } +PUGI__NS_END + +// auto_ptr-like object for exception recovery +PUGI__NS_BEGIN + template struct auto_deleter + { + typedef void (*D)(T*); + + T* data; + D deleter; + + auto_deleter(T* data_, D deleter_): data(data_), deleter(deleter_) + { + } + + ~auto_deleter() + { + if (data) deleter(data); + } + + T* release() + { + T* result = data; + data = 0; + return result; + } + }; +PUGI__NS_END + +#ifdef PUGIXML_COMPACT +PUGI__NS_BEGIN + class compact_hash_table + { + public: + compact_hash_table(): _items(0), _capacity(0), _count(0) + { + } + + void clear() + { + if (_items) + { + xml_memory::deallocate(_items); + _items = 0; + _capacity = 0; + _count = 0; + } + } + + void* find(const void* key) + { + if (_capacity == 0) return 0; + + item_t* item = get_item(key); + assert(item); + assert(item->key == key || (item->key == 0 && item->value == 0)); + + return item->value; + } + + void insert(const void* key, void* value) + { + assert(_capacity != 0 && _count < _capacity - _capacity / 4); + + item_t* item = get_item(key); + assert(item); + + if (item->key == 0) + { + _count++; + item->key = key; + } + + item->value = value; + } + + bool reserve(size_t extra = 16) + { + if (_count + extra >= _capacity - _capacity / 4) + return rehash(_count + extra); + + return true; + } + + private: + struct item_t + { + const void* key; + void* value; + }; + + item_t* _items; + size_t _capacity; + + size_t _count; + + bool rehash(size_t count); + + item_t* get_item(const void* key) + { + assert(key); + assert(_capacity > 0); + + size_t hashmod = _capacity - 1; + size_t bucket = hash(key) & hashmod; + + for (size_t probe = 0; probe <= hashmod; ++probe) + { + item_t& probe_item = _items[bucket]; + + if (probe_item.key == key || probe_item.key == 0) + return &probe_item; + + // hash collision, quadratic probing + bucket = (bucket + probe + 1) & hashmod; + } + + assert(false && "Hash table is full"); // unreachable + return 0; + } + + static PUGI__UNSIGNED_OVERFLOW unsigned int hash(const void* key) + { + unsigned int h = static_cast(reinterpret_cast(key) & 0xffffffff); + + // MurmurHash3 32-bit finalizer + h ^= h >> 16; + h *= 0x85ebca6bu; + h ^= h >> 13; + h *= 0xc2b2ae35u; + h ^= h >> 16; + + return h; + } + }; + + PUGI__FN_NO_INLINE bool compact_hash_table::rehash(size_t count) + { + size_t capacity = 32; + while (count >= capacity - capacity / 4) + capacity *= 2; + + compact_hash_table rt; + rt._capacity = capacity; + rt._items = static_cast(xml_memory::allocate(sizeof(item_t) * capacity)); + + if (!rt._items) + return false; + + memset(rt._items, 0, sizeof(item_t) * capacity); + + for (size_t i = 0; i < _capacity; ++i) + if (_items[i].key) + rt.insert(_items[i].key, _items[i].value); + + if (_items) + xml_memory::deallocate(_items); + + _capacity = capacity; + _items = rt._items; + + assert(_count == rt._count); + + return true; + } + +PUGI__NS_END +#endif + +PUGI__NS_BEGIN +#ifdef PUGIXML_COMPACT + static const uintptr_t xml_memory_block_alignment = 4; +#else + static const uintptr_t xml_memory_block_alignment = sizeof(void*); +#endif + + // extra metadata bits + static const uintptr_t xml_memory_page_contents_shared_mask = 64; + static const uintptr_t xml_memory_page_name_allocated_mask = 32; + static const uintptr_t xml_memory_page_value_allocated_mask = 16; + static const uintptr_t xml_memory_page_type_mask = 15; + + // combined masks for string uniqueness + static const uintptr_t xml_memory_page_name_allocated_or_shared_mask = xml_memory_page_name_allocated_mask | xml_memory_page_contents_shared_mask; + static const uintptr_t xml_memory_page_value_allocated_or_shared_mask = xml_memory_page_value_allocated_mask | xml_memory_page_contents_shared_mask; + +#ifdef PUGIXML_COMPACT + #define PUGI__GETHEADER_IMPL(object, page, flags) // unused + #define PUGI__GETPAGE_IMPL(header) (header).get_page() +#else + #define PUGI__GETHEADER_IMPL(object, page, flags) (((reinterpret_cast(object) - reinterpret_cast(page)) << 8) | (flags)) + // this macro casts pointers through void* to avoid 'cast increases required alignment of target type' warnings + #define PUGI__GETPAGE_IMPL(header) static_cast(const_cast(static_cast(reinterpret_cast(&header) - (header >> 8)))) +#endif + + #define PUGI__GETPAGE(n) PUGI__GETPAGE_IMPL((n)->header) + #define PUGI__NODETYPE(n) static_cast((n)->header & impl::xml_memory_page_type_mask) + + struct xml_allocator; + + struct xml_memory_page + { + static xml_memory_page* construct(void* memory) + { + xml_memory_page* result = static_cast(memory); + + result->allocator = 0; + result->prev = 0; + result->next = 0; + result->busy_size = 0; + result->freed_size = 0; + + #ifdef PUGIXML_COMPACT + result->compact_string_base = 0; + result->compact_shared_parent = 0; + result->compact_page_marker = 0; + #endif + + return result; + } + + xml_allocator* allocator; + + xml_memory_page* prev; + xml_memory_page* next; + + size_t busy_size; + size_t freed_size; + + #ifdef PUGIXML_COMPACT + char_t* compact_string_base; + void* compact_shared_parent; + uint32_t* compact_page_marker; + #endif + }; + + static const size_t xml_memory_page_size = + #ifdef PUGIXML_MEMORY_PAGE_SIZE + (PUGIXML_MEMORY_PAGE_SIZE) + #else + 32768 + #endif + - sizeof(xml_memory_page); + + struct xml_memory_string_header + { + uint16_t page_offset; // offset from page->data + uint16_t full_size; // 0 if string occupies whole page + }; + + struct xml_allocator + { + xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size) + { + #ifdef PUGIXML_COMPACT + _hash = 0; + #endif + } + + xml_memory_page* allocate_page(size_t data_size) + { + size_t size = sizeof(xml_memory_page) + data_size; + + // allocate block with some alignment, leaving memory for worst-case padding + void* memory = xml_memory::allocate(size); + if (!memory) return 0; + + // prepare page structure + xml_memory_page* page = xml_memory_page::construct(memory); + assert(page); + + assert(this == _root->allocator); + page->allocator = this; + + return page; + } + + static void deallocate_page(xml_memory_page* page) + { + xml_memory::deallocate(page); + } + + void* allocate_memory_oob(size_t size, xml_memory_page*& out_page); + + void* allocate_memory(size_t size, xml_memory_page*& out_page) + { + if (PUGI__UNLIKELY(_busy_size + size > xml_memory_page_size)) + return allocate_memory_oob(size, out_page); + + void* buf = reinterpret_cast(_root) + sizeof(xml_memory_page) + _busy_size; + + _busy_size += size; + + out_page = _root; + + return buf; + } + + #ifdef PUGIXML_COMPACT + void* allocate_object(size_t size, xml_memory_page*& out_page) + { + void* result = allocate_memory(size + sizeof(uint32_t), out_page); + if (!result) return 0; + + // adjust for marker + ptrdiff_t offset = static_cast(result) - reinterpret_cast(out_page->compact_page_marker); + + if (PUGI__UNLIKELY(static_cast(offset) >= 256 * xml_memory_block_alignment)) + { + // insert new marker + uint32_t* marker = static_cast(result); + + *marker = static_cast(reinterpret_cast(marker) - reinterpret_cast(out_page)); + out_page->compact_page_marker = marker; + + // since we don't reuse the page space until we reallocate it, we can just pretend that we freed the marker block + // this will make sure deallocate_memory correctly tracks the size + out_page->freed_size += sizeof(uint32_t); + + return marker + 1; + } + else + { + // roll back uint32_t part + _busy_size -= sizeof(uint32_t); + + return result; + } + } + #else + void* allocate_object(size_t size, xml_memory_page*& out_page) + { + return allocate_memory(size, out_page); + } + #endif + + void deallocate_memory(void* ptr, size_t size, xml_memory_page* page) + { + if (page == _root) page->busy_size = _busy_size; + + assert(ptr >= reinterpret_cast(page) + sizeof(xml_memory_page) && ptr < reinterpret_cast(page) + sizeof(xml_memory_page) + page->busy_size); + (void)!ptr; + + page->freed_size += size; + assert(page->freed_size <= page->busy_size); + + if (page->freed_size == page->busy_size) + { + if (page->next == 0) + { + assert(_root == page); + + // top page freed, just reset sizes + page->busy_size = 0; + page->freed_size = 0; + + #ifdef PUGIXML_COMPACT + // reset compact state to maximize efficiency + page->compact_string_base = 0; + page->compact_shared_parent = 0; + page->compact_page_marker = 0; + #endif + + _busy_size = 0; + } + else + { + assert(_root != page); + assert(page->prev); + + // remove from the list + page->prev->next = page->next; + page->next->prev = page->prev; + + // deallocate + deallocate_page(page); + } + } + } + + char_t* allocate_string(size_t length) + { + static const size_t max_encoded_offset = (1 << 16) * xml_memory_block_alignment; + + PUGI__STATIC_ASSERT(xml_memory_page_size <= max_encoded_offset); + + // allocate memory for string and header block + size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); + + // round size up to block alignment boundary + size_t full_size = (size + (xml_memory_block_alignment - 1)) & ~(xml_memory_block_alignment - 1); + + xml_memory_page* page; + xml_memory_string_header* header = static_cast(allocate_memory(full_size, page)); + + if (!header) return 0; + + // setup header + ptrdiff_t page_offset = reinterpret_cast(header) - reinterpret_cast(page) - sizeof(xml_memory_page); + + assert(page_offset % xml_memory_block_alignment == 0); + assert(page_offset >= 0 && static_cast(page_offset) < max_encoded_offset); + header->page_offset = static_cast(static_cast(page_offset) / xml_memory_block_alignment); + + // full_size == 0 for large strings that occupy the whole page + assert(full_size % xml_memory_block_alignment == 0); + assert(full_size < max_encoded_offset || (page->busy_size == full_size && page_offset == 0)); + header->full_size = static_cast(full_size < max_encoded_offset ? full_size / xml_memory_block_alignment : 0); + + // round-trip through void* to avoid 'cast increases required alignment of target type' warning + // header is guaranteed a pointer-sized alignment, which should be enough for char_t + return static_cast(static_cast(header + 1)); + } + + void deallocate_string(char_t* string) + { + // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings + // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string + + // get header + xml_memory_string_header* header = static_cast(static_cast(string)) - 1; + assert(header); + + // deallocate + size_t page_offset = sizeof(xml_memory_page) + header->page_offset * xml_memory_block_alignment; + xml_memory_page* page = reinterpret_cast(static_cast(reinterpret_cast(header) - page_offset)); + + // if full_size == 0 then this string occupies the whole page + size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size * xml_memory_block_alignment; + + deallocate_memory(header, full_size, page); + } + + bool reserve() + { + #ifdef PUGIXML_COMPACT + return _hash->reserve(); + #else + return true; + #endif + } + + xml_memory_page* _root; + size_t _busy_size; + + #ifdef PUGIXML_COMPACT + compact_hash_table* _hash; + #endif + }; + + PUGI__FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) + { + const size_t large_allocation_threshold = xml_memory_page_size / 4; + + xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size); + out_page = page; + + if (!page) return 0; + + if (size <= large_allocation_threshold) + { + _root->busy_size = _busy_size; + + // insert page at the end of linked list + page->prev = _root; + _root->next = page; + _root = page; + + _busy_size = size; + } + else + { + // insert page before the end of linked list, so that it is deleted as soon as possible + // the last page is not deleted even if it's empty (see deallocate_memory) + assert(_root->prev); + + page->prev = _root->prev; + page->next = _root; + + _root->prev->next = page; + _root->prev = page; + + page->busy_size = size; + } + + return reinterpret_cast(page) + sizeof(xml_memory_page); + } +PUGI__NS_END + +#ifdef PUGIXML_COMPACT +PUGI__NS_BEGIN + static const uintptr_t compact_alignment_log2 = 2; + static const uintptr_t compact_alignment = 1 << compact_alignment_log2; + + class compact_header + { + public: + compact_header(xml_memory_page* page, unsigned int flags) + { + PUGI__STATIC_ASSERT(xml_memory_block_alignment == compact_alignment); + + ptrdiff_t offset = (reinterpret_cast(this) - reinterpret_cast(page->compact_page_marker)); + assert(offset % compact_alignment == 0 && static_cast(offset) < 256 * compact_alignment); + + _page = static_cast(offset >> compact_alignment_log2); + _flags = static_cast(flags); + } + + void operator&=(uintptr_t mod) + { + _flags &= static_cast(mod); + } + + void operator|=(uintptr_t mod) + { + _flags |= static_cast(mod); + } + + uintptr_t operator&(uintptr_t mod) const + { + return _flags & mod; + } + + xml_memory_page* get_page() const + { + // round-trip through void* to silence 'cast increases required alignment of target type' warnings + const char* page_marker = reinterpret_cast(this) - (_page << compact_alignment_log2); + const char* page = page_marker - *reinterpret_cast(static_cast(page_marker)); + + return const_cast(reinterpret_cast(static_cast(page))); + } + + private: + unsigned char _page; + unsigned char _flags; + }; + + PUGI__FN xml_memory_page* compact_get_page(const void* object, int header_offset) + { + const compact_header* header = reinterpret_cast(static_cast(object) - header_offset); + + return header->get_page(); + } + + template PUGI__FN_NO_INLINE T* compact_get_value(const void* object) + { + return static_cast(compact_get_page(object, header_offset)->allocator->_hash->find(object)); + } + + template PUGI__FN_NO_INLINE void compact_set_value(const void* object, T* value) + { + compact_get_page(object, header_offset)->allocator->_hash->insert(object, value); + } + + template class compact_pointer + { + public: + compact_pointer(): _data(0) + { + } + + void operator=(const compact_pointer& rhs) + { + *this = rhs + 0; + } + + void operator=(T* value) + { + if (value) + { + // value is guaranteed to be compact-aligned; 'this' is not + // our decoding is based on 'this' aligned to compact alignment downwards (see operator T*) + // so for negative offsets (e.g. -3) we need to adjust the diff by compact_alignment - 1 to + // compensate for arithmetic shift rounding for negative values + ptrdiff_t diff = reinterpret_cast(value) - reinterpret_cast(this); + ptrdiff_t offset = ((diff + int(compact_alignment - 1)) >> compact_alignment_log2) - start; + + if (static_cast(offset) <= 253) + _data = static_cast(offset + 1); + else + { + compact_set_value(this, value); + + _data = 255; + } + } + else + _data = 0; + } + + operator T*() const + { + if (_data) + { + if (_data < 255) + { + uintptr_t base = reinterpret_cast(this) & ~(compact_alignment - 1); + + return reinterpret_cast(base + (_data - 1 + start) * compact_alignment); + } + else + return compact_get_value(this); + } + else + return 0; + } + + T* operator->() const + { + return *this; + } + + private: + unsigned char _data; + }; + + template class compact_pointer_parent + { + public: + compact_pointer_parent(): _data(0) + { + } + + void operator=(const compact_pointer_parent& rhs) + { + *this = rhs + 0; + } + + void operator=(T* value) + { + if (value) + { + // value is guaranteed to be compact-aligned; 'this' is not + // our decoding is based on 'this' aligned to compact alignment downwards (see operator T*) + // so for negative offsets (e.g. -3) we need to adjust the diff by compact_alignment - 1 to + // compensate for arithmetic shift behavior for negative values + ptrdiff_t diff = reinterpret_cast(value) - reinterpret_cast(this); + ptrdiff_t offset = ((diff + int(compact_alignment - 1)) >> compact_alignment_log2) + 65533; + + if (static_cast(offset) <= 65533) + { + _data = static_cast(offset + 1); + } + else + { + xml_memory_page* page = compact_get_page(this, header_offset); + + if (PUGI__UNLIKELY(page->compact_shared_parent == 0)) + page->compact_shared_parent = value; + + if (page->compact_shared_parent == value) + { + _data = 65534; + } + else + { + compact_set_value(this, value); + + _data = 65535; + } + } + } + else + { + _data = 0; + } + } + + operator T*() const + { + if (_data) + { + if (_data < 65534) + { + uintptr_t base = reinterpret_cast(this) & ~(compact_alignment - 1); + + return reinterpret_cast(base + (_data - 1 - 65533) * compact_alignment); + } + else if (_data == 65534) + return static_cast(compact_get_page(this, header_offset)->compact_shared_parent); + else + return compact_get_value(this); + } + else + return 0; + } + + T* operator->() const + { + return *this; + } + + private: + uint16_t _data; + }; + + template class compact_string + { + public: + compact_string(): _data(0) + { + } + + void operator=(const compact_string& rhs) + { + *this = rhs + 0; + } + + void operator=(char_t* value) + { + if (value) + { + xml_memory_page* page = compact_get_page(this, header_offset); + + if (PUGI__UNLIKELY(page->compact_string_base == 0)) + page->compact_string_base = value; + + ptrdiff_t offset = value - page->compact_string_base; + + if (static_cast(offset) < (65535 << 7)) + { + // round-trip through void* to silence 'cast increases required alignment of target type' warnings + uint16_t* base = reinterpret_cast(static_cast(reinterpret_cast(this) - base_offset)); + + if (*base == 0) + { + *base = static_cast((offset >> 7) + 1); + _data = static_cast((offset & 127) + 1); + } + else + { + ptrdiff_t remainder = offset - ((*base - 1) << 7); + + if (static_cast(remainder) <= 253) + { + _data = static_cast(remainder + 1); + } + else + { + compact_set_value(this, value); + + _data = 255; + } + } + } + else + { + compact_set_value(this, value); + + _data = 255; + } + } + else + { + _data = 0; + } + } + + operator char_t*() const + { + if (_data) + { + if (_data < 255) + { + xml_memory_page* page = compact_get_page(this, header_offset); + + // round-trip through void* to silence 'cast increases required alignment of target type' warnings + const uint16_t* base = reinterpret_cast(static_cast(reinterpret_cast(this) - base_offset)); + assert(*base); + + ptrdiff_t offset = ((*base - 1) << 7) + (_data - 1); + + return page->compact_string_base + offset; + } + else + { + return compact_get_value(this); + } + } + else + return 0; + } + + private: + unsigned char _data; + }; +PUGI__NS_END +#endif + +#ifdef PUGIXML_COMPACT +namespace pugi +{ + struct xml_attribute_struct + { + xml_attribute_struct(impl::xml_memory_page* page): header(page, 0), namevalue_base(0) + { + PUGI__STATIC_ASSERT(sizeof(xml_attribute_struct) == 8); + } + + impl::compact_header header; + + uint16_t namevalue_base; + + impl::compact_string<4, 2> name; + impl::compact_string<5, 3> value; + + impl::compact_pointer prev_attribute_c; + impl::compact_pointer next_attribute; + }; + + struct xml_node_struct + { + xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(page, type), namevalue_base(0) + { + PUGI__STATIC_ASSERT(sizeof(xml_node_struct) == 12); + } + + impl::compact_header header; + + uint16_t namevalue_base; + + impl::compact_string<4, 2> name; + impl::compact_string<5, 3> value; + + impl::compact_pointer_parent parent; + + impl::compact_pointer first_child; + + impl::compact_pointer prev_sibling_c; + impl::compact_pointer next_sibling; + + impl::compact_pointer first_attribute; + }; +} +#else +namespace pugi +{ + struct xml_attribute_struct + { + xml_attribute_struct(impl::xml_memory_page* page): name(0), value(0), prev_attribute_c(0), next_attribute(0) + { + header = PUGI__GETHEADER_IMPL(this, page, 0); + } + + uintptr_t header; + + char_t* name; + char_t* value; + + xml_attribute_struct* prev_attribute_c; + xml_attribute_struct* next_attribute; + }; + + struct xml_node_struct + { + xml_node_struct(impl::xml_memory_page* page, xml_node_type type): name(0), value(0), parent(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0) + { + header = PUGI__GETHEADER_IMPL(this, page, type); + } + + uintptr_t header; + + char_t* name; + char_t* value; + + xml_node_struct* parent; + + xml_node_struct* first_child; + + xml_node_struct* prev_sibling_c; + xml_node_struct* next_sibling; + + xml_attribute_struct* first_attribute; + }; +} +#endif + +PUGI__NS_BEGIN + struct xml_extra_buffer + { + char_t* buffer; + xml_extra_buffer* next; + }; + + struct xml_document_struct: public xml_node_struct, public xml_allocator + { + xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0), extra_buffers(0) + { + } + + const char_t* buffer; + + xml_extra_buffer* extra_buffers; + + #ifdef PUGIXML_COMPACT + compact_hash_table hash; + #endif + }; + + template inline xml_allocator& get_allocator(const Object* object) + { + assert(object); + + return *PUGI__GETPAGE(object)->allocator; + } + + template inline xml_document_struct& get_document(const Object* object) + { + assert(object); + + return *static_cast(PUGI__GETPAGE(object)->allocator); + } +PUGI__NS_END + +// Low-level DOM operations +PUGI__NS_BEGIN + inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc) + { + xml_memory_page* page; + void* memory = alloc.allocate_object(sizeof(xml_attribute_struct), page); + if (!memory) return 0; + + return new (memory) xml_attribute_struct(page); + } + + inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type) + { + xml_memory_page* page; + void* memory = alloc.allocate_object(sizeof(xml_node_struct), page); + if (!memory) return 0; + + return new (memory) xml_node_struct(page, type); + } + + inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc) + { + if (a->header & impl::xml_memory_page_name_allocated_mask) + alloc.deallocate_string(a->name); + + if (a->header & impl::xml_memory_page_value_allocated_mask) + alloc.deallocate_string(a->value); + + alloc.deallocate_memory(a, sizeof(xml_attribute_struct), PUGI__GETPAGE(a)); + } + + inline void destroy_node(xml_node_struct* n, xml_allocator& alloc) + { + if (n->header & impl::xml_memory_page_name_allocated_mask) + alloc.deallocate_string(n->name); + + if (n->header & impl::xml_memory_page_value_allocated_mask) + alloc.deallocate_string(n->value); + + for (xml_attribute_struct* attr = n->first_attribute; attr; ) + { + xml_attribute_struct* next = attr->next_attribute; + + destroy_attribute(attr, alloc); + + attr = next; + } + + for (xml_node_struct* child = n->first_child; child; ) + { + xml_node_struct* next = child->next_sibling; + + destroy_node(child, alloc); + + child = next; + } + + alloc.deallocate_memory(n, sizeof(xml_node_struct), PUGI__GETPAGE(n)); + } + + inline void append_node(xml_node_struct* child, xml_node_struct* node) + { + child->parent = node; + + xml_node_struct* head = node->first_child; + + if (head) + { + xml_node_struct* tail = head->prev_sibling_c; + + tail->next_sibling = child; + child->prev_sibling_c = tail; + head->prev_sibling_c = child; + } + else + { + node->first_child = child; + child->prev_sibling_c = child; + } + } + + inline void prepend_node(xml_node_struct* child, xml_node_struct* node) + { + child->parent = node; + + xml_node_struct* head = node->first_child; + + if (head) + { + child->prev_sibling_c = head->prev_sibling_c; + head->prev_sibling_c = child; + } + else + child->prev_sibling_c = child; + + child->next_sibling = head; + node->first_child = child; + } + + inline void insert_node_after(xml_node_struct* child, xml_node_struct* node) + { + xml_node_struct* parent = node->parent; + + child->parent = parent; + + if (node->next_sibling) + node->next_sibling->prev_sibling_c = child; + else + parent->first_child->prev_sibling_c = child; + + child->next_sibling = node->next_sibling; + child->prev_sibling_c = node; + + node->next_sibling = child; + } + + inline void insert_node_before(xml_node_struct* child, xml_node_struct* node) + { + xml_node_struct* parent = node->parent; + + child->parent = parent; + + if (node->prev_sibling_c->next_sibling) + node->prev_sibling_c->next_sibling = child; + else + parent->first_child = child; + + child->prev_sibling_c = node->prev_sibling_c; + child->next_sibling = node; + + node->prev_sibling_c = child; + } + + inline void remove_node(xml_node_struct* node) + { + xml_node_struct* parent = node->parent; + + if (node->next_sibling) + node->next_sibling->prev_sibling_c = node->prev_sibling_c; + else + parent->first_child->prev_sibling_c = node->prev_sibling_c; + + if (node->prev_sibling_c->next_sibling) + node->prev_sibling_c->next_sibling = node->next_sibling; + else + parent->first_child = node->next_sibling; + + node->parent = 0; + node->prev_sibling_c = 0; + node->next_sibling = 0; + } + + inline void append_attribute(xml_attribute_struct* attr, xml_node_struct* node) + { + xml_attribute_struct* head = node->first_attribute; + + if (head) + { + xml_attribute_struct* tail = head->prev_attribute_c; + + tail->next_attribute = attr; + attr->prev_attribute_c = tail; + head->prev_attribute_c = attr; + } + else + { + node->first_attribute = attr; + attr->prev_attribute_c = attr; + } + } + + inline void prepend_attribute(xml_attribute_struct* attr, xml_node_struct* node) + { + xml_attribute_struct* head = node->first_attribute; + + if (head) + { + attr->prev_attribute_c = head->prev_attribute_c; + head->prev_attribute_c = attr; + } + else + attr->prev_attribute_c = attr; + + attr->next_attribute = head; + node->first_attribute = attr; + } + + inline void insert_attribute_after(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node) + { + if (place->next_attribute) + place->next_attribute->prev_attribute_c = attr; + else + node->first_attribute->prev_attribute_c = attr; + + attr->next_attribute = place->next_attribute; + attr->prev_attribute_c = place; + place->next_attribute = attr; + } + + inline void insert_attribute_before(xml_attribute_struct* attr, xml_attribute_struct* place, xml_node_struct* node) + { + if (place->prev_attribute_c->next_attribute) + place->prev_attribute_c->next_attribute = attr; + else + node->first_attribute = attr; + + attr->prev_attribute_c = place->prev_attribute_c; + attr->next_attribute = place; + place->prev_attribute_c = attr; + } + + inline void remove_attribute(xml_attribute_struct* attr, xml_node_struct* node) + { + if (attr->next_attribute) + attr->next_attribute->prev_attribute_c = attr->prev_attribute_c; + else + node->first_attribute->prev_attribute_c = attr->prev_attribute_c; + + if (attr->prev_attribute_c->next_attribute) + attr->prev_attribute_c->next_attribute = attr->next_attribute; + else + node->first_attribute = attr->next_attribute; + + attr->prev_attribute_c = 0; + attr->next_attribute = 0; + } + + PUGI__FN_NO_INLINE xml_node_struct* append_new_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) + { + if (!alloc.reserve()) return 0; + + xml_node_struct* child = allocate_node(alloc, type); + if (!child) return 0; + + append_node(child, node); + + return child; + } + + PUGI__FN_NO_INLINE xml_attribute_struct* append_new_attribute(xml_node_struct* node, xml_allocator& alloc) + { + if (!alloc.reserve()) return 0; + + xml_attribute_struct* attr = allocate_attribute(alloc); + if (!attr) return 0; + + append_attribute(attr, node); + + return attr; + } +PUGI__NS_END + +// Helper classes for code generation +PUGI__NS_BEGIN + struct opt_false + { + enum { value = 0 }; + }; + + struct opt_true + { + enum { value = 1 }; + }; +PUGI__NS_END + +// Unicode utilities +PUGI__NS_BEGIN + inline uint16_t endian_swap(uint16_t value) + { + return static_cast(((value & 0xff) << 8) | (value >> 8)); + } + + inline uint32_t endian_swap(uint32_t value) + { + return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24); + } + + struct utf8_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t ch) + { + // U+0000..U+007F + if (ch < 0x80) return result + 1; + // U+0080..U+07FF + else if (ch < 0x800) return result + 2; + // U+0800..U+FFFF + else return result + 3; + } + + static value_type high(value_type result, uint32_t) + { + // U+10000..U+10FFFF + return result + 4; + } + }; + + struct utf8_writer + { + typedef uint8_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + // U+0000..U+007F + if (ch < 0x80) + { + *result = static_cast(ch); + return result + 1; + } + // U+0080..U+07FF + else if (ch < 0x800) + { + result[0] = static_cast(0xC0 | (ch >> 6)); + result[1] = static_cast(0x80 | (ch & 0x3F)); + return result + 2; + } + // U+0800..U+FFFF + else + { + result[0] = static_cast(0xE0 | (ch >> 12)); + result[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + result[2] = static_cast(0x80 | (ch & 0x3F)); + return result + 3; + } + } + + static value_type high(value_type result, uint32_t ch) + { + // U+10000..U+10FFFF + result[0] = static_cast(0xF0 | (ch >> 18)); + result[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); + result[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + result[3] = static_cast(0x80 | (ch & 0x3F)); + return result + 4; + } + + static value_type any(value_type result, uint32_t ch) + { + return (ch < 0x10000) ? low(result, ch) : high(result, ch); + } + }; + + struct utf16_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t) + { + return result + 1; + } + + static value_type high(value_type result, uint32_t) + { + return result + 2; + } + }; + + struct utf16_writer + { + typedef uint16_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = static_cast(ch); + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + uint32_t msh = static_cast(ch - 0x10000) >> 10; + uint32_t lsh = static_cast(ch - 0x10000) & 0x3ff; + + result[0] = static_cast(0xD800 + msh); + result[1] = static_cast(0xDC00 + lsh); + + return result + 2; + } + + static value_type any(value_type result, uint32_t ch) + { + return (ch < 0x10000) ? low(result, ch) : high(result, ch); + } + }; + + struct utf32_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t) + { + return result + 1; + } + + static value_type high(value_type result, uint32_t) + { + return result + 1; + } + }; + + struct utf32_writer + { + typedef uint32_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + + static value_type any(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + }; + + struct latin1_writer + { + typedef uint8_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = static_cast(ch > 255 ? '?' : ch); + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + (void)ch; + + *result = '?'; + + return result + 1; + } + }; + + struct utf8_decoder + { + typedef uint8_t type; + + template static inline typename Traits::value_type process(const uint8_t* data, size_t size, typename Traits::value_type result, Traits) + { + const uint8_t utf8_byte_mask = 0x3f; + + while (size) + { + uint8_t lead = *data; + + // 0xxxxxxx -> U+0000..U+007F + if (lead < 0x80) + { + result = Traits::low(result, lead); + data += 1; + size -= 1; + + // process aligned single-byte (ascii) blocks + if ((reinterpret_cast(data) & 3) == 0) + { + // round-trip through void* to silence 'cast increases required alignment of target type' warnings + while (size >= 4 && (*static_cast(static_cast(data)) & 0x80808080) == 0) + { + result = Traits::low(result, data[0]); + result = Traits::low(result, data[1]); + result = Traits::low(result, data[2]); + result = Traits::low(result, data[3]); + data += 4; + size -= 4; + } + } + } + // 110xxxxx -> U+0080..U+07FF + else if (static_cast(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80) + { + result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask)); + data += 2; + size -= 2; + } + // 1110xxxx -> U+0800-U+FFFF + else if (static_cast(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80) + { + result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask)); + data += 3; + size -= 3; + } + // 11110xxx -> U+10000..U+10FFFF + else if (static_cast(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80) + { + result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask)); + data += 4; + size -= 4; + } + // 10xxxxxx or 11111xxx -> invalid + else + { + data += 1; + size -= 1; + } + } + + return result; + } + }; + + template struct utf16_decoder + { + typedef uint16_t type; + + template static inline typename Traits::value_type process(const uint16_t* data, size_t size, typename Traits::value_type result, Traits) + { + while (size) + { + uint16_t lead = opt_swap::value ? endian_swap(*data) : *data; + + // U+0000..U+D7FF + if (lead < 0xD800) + { + result = Traits::low(result, lead); + data += 1; + size -= 1; + } + // U+E000..U+FFFF + else if (static_cast(lead - 0xE000) < 0x2000) + { + result = Traits::low(result, lead); + data += 1; + size -= 1; + } + // surrogate pair lead + else if (static_cast(lead - 0xD800) < 0x400 && size >= 2) + { + uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1]; + + if (static_cast(next - 0xDC00) < 0x400) + { + result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff)); + data += 2; + size -= 2; + } + else + { + data += 1; + size -= 1; + } + } + else + { + data += 1; + size -= 1; + } + } + + return result; + } + }; + + template struct utf32_decoder + { + typedef uint32_t type; + + template static inline typename Traits::value_type process(const uint32_t* data, size_t size, typename Traits::value_type result, Traits) + { + while (size) + { + uint32_t lead = opt_swap::value ? endian_swap(*data) : *data; + + // U+0000..U+FFFF + if (lead < 0x10000) + { + result = Traits::low(result, lead); + data += 1; + size -= 1; + } + // U+10000..U+10FFFF + else + { + result = Traits::high(result, lead); + data += 1; + size -= 1; + } + } + + return result; + } + }; + + struct latin1_decoder + { + typedef uint8_t type; + + template static inline typename Traits::value_type process(const uint8_t* data, size_t size, typename Traits::value_type result, Traits) + { + while (size) + { + result = Traits::low(result, *data); + data += 1; + size -= 1; + } + + return result; + } + }; + + template struct wchar_selector; + + template <> struct wchar_selector<2> + { + typedef uint16_t type; + typedef utf16_counter counter; + typedef utf16_writer writer; + typedef utf16_decoder decoder; + }; + + template <> struct wchar_selector<4> + { + typedef uint32_t type; + typedef utf32_counter counter; + typedef utf32_writer writer; + typedef utf32_decoder decoder; + }; + + typedef wchar_selector::counter wchar_counter; + typedef wchar_selector::writer wchar_writer; + + struct wchar_decoder + { + typedef wchar_t type; + + template static inline typename Traits::value_type process(const wchar_t* data, size_t size, typename Traits::value_type result, Traits traits) + { + typedef wchar_selector::decoder decoder; + + return decoder::process(reinterpret_cast(data), size, result, traits); + } + }; + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) + { + for (size_t i = 0; i < length; ++i) + result[i] = static_cast(endian_swap(static_cast::type>(data[i]))); + } +#endif +PUGI__NS_END + +PUGI__NS_BEGIN + enum chartype_t + { + ct_parse_pcdata = 1, // \0, &, \r, < + ct_parse_attr = 2, // \0, &, \r, ', " + ct_parse_attr_ws = 4, // \0, &, \r, ', ", \n, tab + ct_space = 8, // \r, \n, space, tab + ct_parse_cdata = 16, // \0, ], >, \r + ct_parse_comment = 32, // \0, -, >, \r + ct_symbol = 64, // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, . + ct_start_symbol = 128 // Any symbol > 127, a-z, A-Z, _, : + }; + + static const unsigned char chartype_table[256] = + { + 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 + 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47 + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63 + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79 + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95 + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111 + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127 + + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+ + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192 + }; + + enum chartypex_t + { + ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, > + ctx_special_attr = 2, // Any symbol >= 0 and < 32, &, <, ", ' + ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _ + ctx_digit = 8, // 0-9 + ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, . + }; + + static const unsigned char chartypex_table[256] = + { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 3, 3, 2, 3, 3, // 0-15 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31 + 0, 0, 2, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47 + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 1, 0, // 48-63 + + 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 64-79 + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 20, // 80-95 + 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 96-111 + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, // 112-127 + + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 128+ + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 + }; + +#ifdef PUGIXML_WCHAR_MODE + #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) ((static_cast(c) < 128 ? table[static_cast(c)] : table[128]) & (ct)) +#else + #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast(c)] & (ct)) +#endif + + #define PUGI__IS_CHARTYPE(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartype_table) + #define PUGI__IS_CHARTYPEX(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartypex_table) + + PUGI__FN bool is_little_endian() + { + unsigned int ui = 1; + + return *reinterpret_cast(&ui) == 1; + } + + PUGI__FN xml_encoding get_wchar_encoding() + { + PUGI__STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); + + if (sizeof(wchar_t) == 2) + return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + else + return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + } + + PUGI__FN bool parse_declaration_encoding(const uint8_t* data, size_t size, const uint8_t*& out_encoding, size_t& out_length) + { + #define PUGI__SCANCHAR(ch) { if (offset >= size || data[offset] != ch) return false; offset++; } + #define PUGI__SCANCHARTYPE(ct) { while (offset < size && PUGI__IS_CHARTYPE(data[offset], ct)) offset++; } + + // check if we have a non-empty XML declaration + if (size < 6 || !((data[0] == '<') & (data[1] == '?') & (data[2] == 'x') & (data[3] == 'm') & (data[4] == 'l') && PUGI__IS_CHARTYPE(data[5], ct_space))) + return false; + + // scan XML declaration until the encoding field + for (size_t i = 6; i + 1 < size; ++i) + { + // declaration can not contain ? in quoted values + if (data[i] == '?') + return false; + + if (data[i] == 'e' && data[i + 1] == 'n') + { + size_t offset = i; + + // encoding follows the version field which can't contain 'en' so this has to be the encoding if XML is well formed + PUGI__SCANCHAR('e'); PUGI__SCANCHAR('n'); PUGI__SCANCHAR('c'); PUGI__SCANCHAR('o'); + PUGI__SCANCHAR('d'); PUGI__SCANCHAR('i'); PUGI__SCANCHAR('n'); PUGI__SCANCHAR('g'); + + // S? = S? + PUGI__SCANCHARTYPE(ct_space); + PUGI__SCANCHAR('='); + PUGI__SCANCHARTYPE(ct_space); + + // the only two valid delimiters are ' and " + uint8_t delimiter = (offset < size && data[offset] == '"') ? '"' : '\''; + + PUGI__SCANCHAR(delimiter); + + size_t start = offset; + + out_encoding = data + offset; + + PUGI__SCANCHARTYPE(ct_symbol); + + out_length = offset - start; + + PUGI__SCANCHAR(delimiter); + + return true; + } + } + + return false; + + #undef PUGI__SCANCHAR + #undef PUGI__SCANCHARTYPE + } + + PUGI__FN xml_encoding guess_buffer_encoding(const uint8_t* data, size_t size) + { + // skip encoding autodetection if input buffer is too small + if (size < 4) return encoding_utf8; + + uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3]; + + // look for BOM in first few bytes + if (d0 == 0 && d1 == 0 && d2 == 0xfe && d3 == 0xff) return encoding_utf32_be; + if (d0 == 0xff && d1 == 0xfe && d2 == 0 && d3 == 0) return encoding_utf32_le; + if (d0 == 0xfe && d1 == 0xff) return encoding_utf16_be; + if (d0 == 0xff && d1 == 0xfe) return encoding_utf16_le; + if (d0 == 0xef && d1 == 0xbb && d2 == 0xbf) return encoding_utf8; + + // look for <, (contents); + + return guess_buffer_encoding(data, size); + } + + PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + size_t length = size / sizeof(char_t); + + if (is_mutable) + { + out_buffer = static_cast(const_cast(contents)); + out_length = length; + } + else + { + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + if (contents) + memcpy(buffer, contents, length * sizeof(char_t)); + else + assert(length == 0); + + buffer[length] = 0; + + out_buffer = buffer; + out_length = length + 1; + } + + return true; + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) + { + return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) || + (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); + } + + PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + const char_t* data = static_cast(contents); + size_t length = size / sizeof(char_t); + + if (is_mutable) + { + char_t* buffer = const_cast(data); + + convert_wchar_endian_swap(buffer, data, length); + + out_buffer = buffer; + out_length = length; + } + else + { + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + convert_wchar_endian_swap(buffer, data, length); + buffer[length] = 0; + + out_buffer = buffer; + out_length = length + 1; + } + + return true; + } + + template PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) + { + const typename D::type* data = static_cast(contents); + size_t data_length = size / sizeof(typename D::type); + + // first pass: get length in wchar_t units + size_t length = D::process(data, data_length, 0, wchar_counter()); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf16 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast(buffer); + wchar_writer::value_type oend = D::process(data, data_length, obegin, wchar_writer()); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) + { + // get native encoding + xml_encoding wchar_encoding = get_wchar_encoding(); + + // fast path: no conversion required + if (encoding == wchar_encoding) + return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // only endian-swapping is required + if (need_endian_swap_utf(encoding, wchar_encoding)) + return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable); + + // source encoding is utf8 + if (encoding == encoding_utf8) + return convert_buffer_generic(out_buffer, out_length, contents, size, utf8_decoder()); + + // source encoding is utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + return (native_encoding == encoding) ? + convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()) : + convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()); + } + + // source encoding is utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return (native_encoding == encoding) ? + convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()) : + convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()); + } + + // source encoding is latin1 + if (encoding == encoding_latin1) + return convert_buffer_generic(out_buffer, out_length, contents, size, latin1_decoder()); + + assert(false && "Invalid encoding"); // unreachable + return false; + } +#else + template PUGI__FN bool convert_buffer_generic(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, D) + { + const typename D::type* data = static_cast(contents); + size_t data_length = size / sizeof(typename D::type); + + // first pass: get length in utf8 units + size_t length = D::process(data, data_length, 0, utf8_counter()); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf16 input to utf8 + uint8_t* obegin = reinterpret_cast(buffer); + uint8_t* oend = D::process(data, data_length, obegin, utf8_writer()); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) + { + for (size_t i = 0; i < size; ++i) + if (data[i] > 127) + return i; + + return size; + } + + PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + const uint8_t* data = static_cast(contents); + size_t data_length = size; + + // get size of prefix that does not need utf8 conversion + size_t prefix_length = get_latin1_7bit_prefix_length(data, data_length); + assert(prefix_length <= data_length); + + const uint8_t* postfix = data + prefix_length; + size_t postfix_length = data_length - prefix_length; + + // if no conversion is needed, just return the original buffer + if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // first pass: get length in utf8 units + size_t length = prefix_length + latin1_decoder::process(postfix, postfix_length, 0, utf8_counter()); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert latin1 input to utf8 + memcpy(buffer, data, prefix_length); + + uint8_t* obegin = reinterpret_cast(buffer); + uint8_t* oend = latin1_decoder::process(postfix, postfix_length, obegin + prefix_length, utf8_writer()); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) + { + // fast path: no conversion required + if (encoding == encoding_utf8) + return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // source encoding is utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + return (native_encoding == encoding) ? + convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()) : + convert_buffer_generic(out_buffer, out_length, contents, size, utf16_decoder()); + } + + // source encoding is utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return (native_encoding == encoding) ? + convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()) : + convert_buffer_generic(out_buffer, out_length, contents, size, utf32_decoder()); + } + + // source encoding is latin1 + if (encoding == encoding_latin1) + return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable); + + assert(false && "Invalid encoding"); // unreachable + return false; + } +#endif + + PUGI__FN size_t as_utf8_begin(const wchar_t* str, size_t length) + { + // get length in utf8 characters + return wchar_decoder::process(str, length, 0, utf8_counter()); + } + + PUGI__FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) + { + // convert to utf8 + uint8_t* begin = reinterpret_cast(buffer); + uint8_t* end = wchar_decoder::process(str, length, begin, utf8_writer()); + + assert(begin + size == end); + (void)!end; + (void)!size; + } + +#ifndef PUGIXML_NO_STL + PUGI__FN std::string as_utf8_impl(const wchar_t* str, size_t length) + { + // first pass: get length in utf8 characters + size_t size = as_utf8_begin(str, length); + + // allocate resulting string + std::string result; + result.resize(size); + + // second pass: convert to utf8 + if (size > 0) as_utf8_end(&result[0], size, str, length); + + return result; + } + + PUGI__FN std::basic_string as_wide_impl(const char* str, size_t size) + { + const uint8_t* data = reinterpret_cast(str); + + // first pass: get length in wchar_t units + size_t length = utf8_decoder::process(data, size, 0, wchar_counter()); + + // allocate resulting string + std::basic_string result; + result.resize(length); + + // second pass: convert to wchar_t + if (length > 0) + { + wchar_writer::value_type begin = reinterpret_cast(&result[0]); + wchar_writer::value_type end = utf8_decoder::process(data, size, begin, wchar_writer()); + + assert(begin + length == end); + (void)!end; + } + + return result; + } +#endif + + template + inline bool strcpy_insitu_allow(size_t length, const Header& header, uintptr_t header_mask, char_t* target) + { + // never reuse shared memory + if (header & xml_memory_page_contents_shared_mask) return false; + + size_t target_length = strlength(target); + + // always reuse document buffer memory if possible + if ((header & header_mask) == 0) return target_length >= length; + + // reuse heap memory if waste is not too great + const size_t reuse_threshold = 32; + + return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2); + } + + template + PUGI__FN bool strcpy_insitu(String& dest, Header& header, uintptr_t header_mask, const char_t* source, size_t source_length) + { + if (source_length == 0) + { + // empty string and null pointer are equivalent, so just deallocate old memory + xml_allocator* alloc = PUGI__GETPAGE_IMPL(header)->allocator; + + if (header & header_mask) alloc->deallocate_string(dest); + + // mark the string as not allocated + dest = 0; + header &= ~header_mask; + + return true; + } + else if (dest && strcpy_insitu_allow(source_length, header, header_mask, dest)) + { + // we can reuse old buffer, so just copy the new data (including zero terminator) + memcpy(dest, source, source_length * sizeof(char_t)); + dest[source_length] = 0; + + return true; + } + else + { + xml_allocator* alloc = PUGI__GETPAGE_IMPL(header)->allocator; + + if (!alloc->reserve()) return false; + + // allocate new buffer + char_t* buf = alloc->allocate_string(source_length + 1); + if (!buf) return false; + + // copy the string (including zero terminator) + memcpy(buf, source, source_length * sizeof(char_t)); + buf[source_length] = 0; + + // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures) + if (header & header_mask) alloc->deallocate_string(dest); + + // the string is now allocated, so set the flag + dest = buf; + header |= header_mask; + + return true; + } + } + + struct gap + { + char_t* end; + size_t size; + + gap(): end(0), size(0) + { + } + + // Push new gap, move s count bytes further (skipping the gap). + // Collapse previous gap. + void push(char_t*& s, size_t count) + { + if (end) // there was a gap already; collapse it + { + // Move [old_gap_end, new_gap_start) to [old_gap_start, ...) + assert(s >= end); + memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); + } + + s += count; // end of current gap + + // "merge" two gaps + end = s; + size += count; + } + + // Collapse all gaps, return past-the-end pointer + char_t* flush(char_t* s) + { + if (end) + { + // Move [old_gap_end, current_pos) to [old_gap_start, ...) + assert(s >= end); + memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); + + return s - size; + } + else return s; + } + }; + + PUGI__FN char_t* strconv_escape(char_t* s, gap& g) + { + char_t* stre = s + 1; + + switch (*stre) + { + case '#': // &#... + { + unsigned int ucsc = 0; + + if (stre[1] == 'x') // &#x... (hex code) + { + stre += 2; + + char_t ch = *stre; + + if (ch == ';') return stre; + + for (;;) + { + if (static_cast(ch - '0') <= 9) + ucsc = 16 * ucsc + (ch - '0'); + else if (static_cast((ch | ' ') - 'a') <= 5) + ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10); + else if (ch == ';') + break; + else // cancel + return stre; + + ch = *++stre; + } + + ++stre; + } + else // &#... (dec code) + { + char_t ch = *++stre; + + if (ch == ';') return stre; + + for (;;) + { + if (static_cast(ch - '0') <= 9) + ucsc = 10 * ucsc + (ch - '0'); + else if (ch == ';') + break; + else // cancel + return stre; + + ch = *++stre; + } + + ++stre; + } + + #ifdef PUGIXML_WCHAR_MODE + s = reinterpret_cast(wchar_writer::any(reinterpret_cast(s), ucsc)); + #else + s = reinterpret_cast(utf8_writer::any(reinterpret_cast(s), ucsc)); + #endif + + g.push(s, stre - s); + return stre; + } + + case 'a': // &a + { + ++stre; + + if (*stre == 'm') // &am + { + if (*++stre == 'p' && *++stre == ';') // & + { + *s++ = '&'; + ++stre; + + g.push(s, stre - s); + return stre; + } + } + else if (*stre == 'p') // &ap + { + if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // ' + { + *s++ = '\''; + ++stre; + + g.push(s, stre - s); + return stre; + } + } + break; + } + + case 'g': // &g + { + if (*++stre == 't' && *++stre == ';') // > + { + *s++ = '>'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + case 'l': // &l + { + if (*++stre == 't' && *++stre == ';') // < + { + *s++ = '<'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + case 'q': // &q + { + if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // " + { + *s++ = '"'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + default: + break; + } + + return stre; + } + + // Parser utilities + #define PUGI__ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e))) + #define PUGI__SKIPWS() { while (PUGI__IS_CHARTYPE(*s, ct_space)) ++s; } + #define PUGI__OPTSET(OPT) ( optmsk & (OPT) ) + #define PUGI__PUSHNODE(TYPE) { cursor = append_new_node(cursor, *alloc, TYPE); if (!cursor) PUGI__THROW_ERROR(status_out_of_memory, s); } + #define PUGI__POPNODE() { cursor = cursor->parent; } + #define PUGI__SCANFOR(X) { while (*s != 0 && !(X)) ++s; } + #define PUGI__SCANWHILE(X) { while (X) ++s; } + #define PUGI__SCANWHILE_UNROLL(X) { for (;;) { char_t ss = s[0]; if (PUGI__UNLIKELY(!(X))) { break; } ss = s[1]; if (PUGI__UNLIKELY(!(X))) { s += 1; break; } ss = s[2]; if (PUGI__UNLIKELY(!(X))) { s += 2; break; } ss = s[3]; if (PUGI__UNLIKELY(!(X))) { s += 3; break; } s += 4; } } + #define PUGI__ENDSEG() { ch = *s; *s = 0; ++s; } + #define PUGI__THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast(0) + #define PUGI__CHECK_ERROR(err, m) { if (*s == 0) PUGI__THROW_ERROR(err, m); } + + PUGI__FN char_t* strconv_comment(char_t* s, char_t endch) + { + gap g; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_comment)); + + if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (s[0] == '-' && s[1] == '-' && PUGI__ENDSWITH(s[2], '>')) // comment ends here + { + *g.flush(s) = 0; + + return s + (s[2] == '>' ? 3 : 2); + } + else if (*s == 0) + { + return 0; + } + else ++s; + } + } + + PUGI__FN char_t* strconv_cdata(char_t* s, char_t endch) + { + gap g; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_cdata)); + + if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')) // CDATA ends here + { + *g.flush(s) = 0; + + return s + 1; + } + else if (*s == 0) + { + return 0; + } + else ++s; + } + } + + typedef char_t* (*strconv_pcdata_t)(char_t*); + + template struct strconv_pcdata_impl + { + static char_t* parse(char_t* s) + { + gap g; + + char_t* begin = s; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_pcdata)); + + if (*s == '<') // PCDATA ends here + { + char_t* end = g.flush(s); + + if (opt_trim::value) + while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) + --end; + + *end = 0; + + return s + 1; + } + else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (*s == 0) + { + char_t* end = g.flush(s); + + if (opt_trim::value) + while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) + --end; + + *end = 0; + + return s; + } + else ++s; + } + } + }; + + PUGI__FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) + { + PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_trim_pcdata == 0x0800); + + switch (((optmask >> 4) & 3) | ((optmask >> 9) & 4)) // get bitmask for flags (trim eol escapes); this simultaneously checks 3 options from assertion above + { + case 0: return strconv_pcdata_impl::parse; + case 1: return strconv_pcdata_impl::parse; + case 2: return strconv_pcdata_impl::parse; + case 3: return strconv_pcdata_impl::parse; + case 4: return strconv_pcdata_impl::parse; + case 5: return strconv_pcdata_impl::parse; + case 6: return strconv_pcdata_impl::parse; + case 7: return strconv_pcdata_impl::parse; + default: assert(false); return 0; // unreachable + } + } + + typedef char_t* (*strconv_attribute_t)(char_t*, char_t); + + template struct strconv_attribute_impl + { + static char_t* parse_wnorm(char_t* s, char_t end_quote) + { + gap g; + + // trim leading whitespaces + if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + char_t* str = s; + + do ++str; + while (PUGI__IS_CHARTYPE(*str, ct_space)); + + g.push(s, str - s); + } + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr_ws | ct_space)); + + if (*s == end_quote) + { + char_t* str = g.flush(s); + + do *str-- = 0; + while (PUGI__IS_CHARTYPE(*str, ct_space)); + + return s + 1; + } + else if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + *s++ = ' '; + + if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + char_t* str = s + 1; + while (PUGI__IS_CHARTYPE(*str, ct_space)) ++str; + + g.push(s, str - s); + } + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_wconv(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr_ws)); + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + if (*s == '\r') + { + *s++ = ' '; + + if (*s == '\n') g.push(s, 1); + } + else *s++ = ' '; + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_eol(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr)); + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (*s == '\r') + { + *s++ = '\n'; + + if (*s == '\n') g.push(s, 1); + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_simple(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPE(ss, ct_parse_attr)); + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + }; + + PUGI__FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) + { + PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); + + switch ((optmask >> 4) & 15) // get bitmask for flags (wnorm wconv eol escapes); this simultaneously checks 4 options from assertion above + { + case 0: return strconv_attribute_impl::parse_simple; + case 1: return strconv_attribute_impl::parse_simple; + case 2: return strconv_attribute_impl::parse_eol; + case 3: return strconv_attribute_impl::parse_eol; + case 4: return strconv_attribute_impl::parse_wconv; + case 5: return strconv_attribute_impl::parse_wconv; + case 6: return strconv_attribute_impl::parse_wconv; + case 7: return strconv_attribute_impl::parse_wconv; + case 8: return strconv_attribute_impl::parse_wnorm; + case 9: return strconv_attribute_impl::parse_wnorm; + case 10: return strconv_attribute_impl::parse_wnorm; + case 11: return strconv_attribute_impl::parse_wnorm; + case 12: return strconv_attribute_impl::parse_wnorm; + case 13: return strconv_attribute_impl::parse_wnorm; + case 14: return strconv_attribute_impl::parse_wnorm; + case 15: return strconv_attribute_impl::parse_wnorm; + default: assert(false); return 0; // unreachable + } + } + + inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0) + { + xml_parse_result result; + result.status = status; + result.offset = offset; + + return result; + } + + struct xml_parser + { + xml_allocator* alloc; + char_t* error_offset; + xml_parse_status error_status; + + xml_parser(xml_allocator* alloc_): alloc(alloc_), error_offset(0), error_status(status_ok) + { + } + + // DOCTYPE consists of nested sections of the following possible types: + // , , "...", '...' + // + // + // First group can not contain nested groups + // Second group can contain nested groups of the same type + // Third group can contain all other groups + char_t* parse_doctype_primitive(char_t* s) + { + if (*s == '"' || *s == '\'') + { + // quoted string + char_t ch = *s++; + PUGI__SCANFOR(*s == ch); + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s++; + } + else if (s[0] == '<' && s[1] == '?') + { + // + s += 2; + PUGI__SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s += 2; + } + else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') + { + s += 4; + PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s += 3; + } + else PUGI__THROW_ERROR(status_bad_doctype, s); + + return s; + } + + char_t* parse_doctype_ignore(char_t* s) + { + size_t depth = 0; + + assert(s[0] == '<' && s[1] == '!' && s[2] == '['); + s += 3; + + while (*s) + { + if (s[0] == '<' && s[1] == '!' && s[2] == '[') + { + // nested ignore section + s += 3; + depth++; + } + else if (s[0] == ']' && s[1] == ']' && s[2] == '>') + { + // ignore section end + s += 3; + + if (depth == 0) + return s; + + depth--; + } + else s++; + } + + PUGI__THROW_ERROR(status_bad_doctype, s); + } + + char_t* parse_doctype_group(char_t* s, char_t endch) + { + size_t depth = 0; + + assert((s[0] == '<' || s[0] == 0) && s[1] == '!'); + s += 2; + + while (*s) + { + if (s[0] == '<' && s[1] == '!' && s[2] != '-') + { + if (s[2] == '[') + { + // ignore + s = parse_doctype_ignore(s); + if (!s) return s; + } + else + { + // some control group + s += 2; + depth++; + } + } + else if (s[0] == '<' || s[0] == '"' || s[0] == '\'') + { + // unknown tag (forbidden), or some primitive group + s = parse_doctype_primitive(s); + if (!s) return s; + } + else if (*s == '>') + { + if (depth == 0) + return s; + + depth--; + s++; + } + else s++; + } + + if (depth != 0 || endch != '>') PUGI__THROW_ERROR(status_bad_doctype, s); + + return s; + } + + char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch) + { + // parse node contents, starting with exclamation mark + ++s; + + if (*s == '-') // 'value = s; // Save the offset. + } + + if (PUGI__OPTSET(parse_eol) && PUGI__OPTSET(parse_comments)) + { + s = strconv_comment(s, endch); + + if (!s) PUGI__THROW_ERROR(status_bad_comment, cursor->value); + } + else + { + // Scan for terminating '-->'. + PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && PUGI__ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_comment, s); + + if (PUGI__OPTSET(parse_comments)) + *s = 0; // Zero-terminate this segment at the first terminating '-'. + + s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'. + } + } + else PUGI__THROW_ERROR(status_bad_comment, s); + } + else if (*s == '[') + { + // 'value = s; // Save the offset. + + if (PUGI__OPTSET(parse_eol)) + { + s = strconv_cdata(s, endch); + + if (!s) PUGI__THROW_ERROR(status_bad_cdata, cursor->value); + } + else + { + // Scan for terminating ']]>'. + PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_cdata, s); + + *s++ = 0; // Zero-terminate this segment. + } + } + else // Flagged for discard, but we still have to scan for the terminator. + { + // Scan for terminating ']]>'. + PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && PUGI__ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_cdata, s); + + ++s; + } + + s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'. + } + else PUGI__THROW_ERROR(status_bad_cdata, s); + } + else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && PUGI__ENDSWITH(s[6], 'E')) + { + s -= 2; + + if (cursor->parent) PUGI__THROW_ERROR(status_bad_doctype, s); + + char_t* mark = s + 9; + + s = parse_doctype_group(s, endch); + if (!s) return s; + + assert((*s == 0 && endch == '>') || *s == '>'); + if (*s) *s++ = 0; + + if (PUGI__OPTSET(parse_doctype)) + { + while (PUGI__IS_CHARTYPE(*mark, ct_space)) ++mark; + + PUGI__PUSHNODE(node_doctype); + + cursor->value = mark; + } + } + else if (*s == 0 && endch == '-') PUGI__THROW_ERROR(status_bad_comment, s); + else if (*s == 0 && endch == '[') PUGI__THROW_ERROR(status_bad_cdata, s); + else PUGI__THROW_ERROR(status_unrecognized_tag, s); + + return s; + } + + char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch) + { + // load into registers + xml_node_struct* cursor = ref_cursor; + char_t ch = 0; + + // parse node contents, starting with question mark + ++s; + + // read PI target + char_t* target = s; + + if (!PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_pi, s); + + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); + PUGI__CHECK_ERROR(status_bad_pi, s); + + // determine node type; stricmp / strcasecmp is not portable + bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s; + + if (declaration ? PUGI__OPTSET(parse_declaration) : PUGI__OPTSET(parse_pi)) + { + if (declaration) + { + // disallow non top-level declarations + if (cursor->parent) PUGI__THROW_ERROR(status_bad_pi, s); + + PUGI__PUSHNODE(node_declaration); + } + else + { + PUGI__PUSHNODE(node_pi); + } + + cursor->name = target; + + PUGI__ENDSEG(); + + // parse value/attributes + if (ch == '?') + { + // empty node + if (!PUGI__ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_pi, s); + s += (*s == '>'); + + PUGI__POPNODE(); + } + else if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + PUGI__SKIPWS(); + + // scan for tag end + char_t* value = s; + + PUGI__SCANFOR(s[0] == '?' && PUGI__ENDSWITH(s[1], '>')); + PUGI__CHECK_ERROR(status_bad_pi, s); + + if (declaration) + { + // replace ending ? with / so that 'element' terminates properly + *s = '/'; + + // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES + s = value; + } + else + { + // store value and step over > + cursor->value = value; + + PUGI__POPNODE(); + + PUGI__ENDSEG(); + + s += (*s == '>'); + } + } + else PUGI__THROW_ERROR(status_bad_pi, s); + } + else + { + // scan for tag end + PUGI__SCANFOR(s[0] == '?' && PUGI__ENDSWITH(s[1], '>')); + PUGI__CHECK_ERROR(status_bad_pi, s); + + s += (s[1] == '>' ? 2 : 1); + } + + // store from registers + ref_cursor = cursor; + + return s; + } + + char_t* parse_tree(char_t* s, xml_node_struct* root, unsigned int optmsk, char_t endch) + { + strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk); + strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk); + + char_t ch = 0; + xml_node_struct* cursor = root; + char_t* mark = s; + + while (*s != 0) + { + if (*s == '<') + { + ++s; + + LOC_TAG: + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' + { + PUGI__PUSHNODE(node_element); // Append a new node to the tree. + + cursor->name = s; + + PUGI__SCANWHILE_UNROLL(PUGI__IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. + PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + + if (ch == '>') + { + // end of tag + } + else if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + LOC_ATTRIBUTES: + while (true) + { + PUGI__SKIPWS(); // Eat any whitespace. + + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // <... #... + { + xml_attribute_struct* a = append_new_attribute(cursor, *alloc); // Make space for this attribute. + if (!a) PUGI__THROW_ERROR(status_out_of_memory, s); + + a->name = s; // Save the offset. + + PUGI__SCANWHILE_UNROLL(PUGI__IS_CHARTYPE(ss, ct_symbol)); // Scan for a terminator. + PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + + if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + PUGI__SKIPWS(); // Eat any whitespace. + + ch = *s; + ++s; + } + + if (ch == '=') // '<... #=...' + { + PUGI__SKIPWS(); // Eat any whitespace. + + if (*s == '"' || *s == '\'') // '<... #="...' + { + ch = *s; // Save quote char to avoid breaking on "''" -or- '""'. + ++s; // Step over the quote. + a->value = s; // Save the offset. + + s = strconv_attribute(s, ch); + + if (!s) PUGI__THROW_ERROR(status_bad_attribute, a->value); + + // After this line the loop continues from the start; + // Whitespaces, / and > are ok, symbols and EOF are wrong, + // everything else will be detected + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_attribute, s); + } + else PUGI__THROW_ERROR(status_bad_attribute, s); + } + else PUGI__THROW_ERROR(status_bad_attribute, s); + } + else if (*s == '/') + { + ++s; + + if (*s == '>') + { + PUGI__POPNODE(); + s++; + break; + } + else if (*s == 0 && endch == '>') + { + PUGI__POPNODE(); + break; + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + else if (*s == '>') + { + ++s; + + break; + } + else if (*s == 0 && endch == '>') + { + break; + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + + // !!! + } + else if (ch == '/') // '<#.../' + { + if (!PUGI__ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_start_element, s); + + PUGI__POPNODE(); // Pop. + + s += (*s == '>'); + } + else if (ch == 0) + { + // we stepped over null terminator, backtrack & handle closing tag + --s; + + if (endch != '>') PUGI__THROW_ERROR(status_bad_start_element, s); + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + else if (*s == '/') + { + ++s; + + mark = s; + + char_t* name = cursor->name; + if (!name) PUGI__THROW_ERROR(status_end_element_mismatch, mark); + + while (PUGI__IS_CHARTYPE(*s, ct_symbol)) + { + if (*s++ != *name++) PUGI__THROW_ERROR(status_end_element_mismatch, mark); + } + + if (*name) + { + if (*s == 0 && name[0] == endch && name[1] == 0) PUGI__THROW_ERROR(status_bad_end_element, s); + else PUGI__THROW_ERROR(status_end_element_mismatch, mark); + } + + PUGI__POPNODE(); // Pop. + + PUGI__SKIPWS(); + + if (*s == 0) + { + if (endch != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + } + else + { + if (*s != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + ++s; + } + } + else if (*s == '?') // 'first_child) continue; + } + } + + if (!PUGI__OPTSET(parse_trim_pcdata)) + s = mark; + + if (cursor->parent || PUGI__OPTSET(parse_fragment)) + { + if (PUGI__OPTSET(parse_embed_pcdata) && cursor->parent && !cursor->first_child && !cursor->value) + { + cursor->value = s; // Save the offset. + } + else + { + PUGI__PUSHNODE(node_pcdata); // Append a new node on the tree. + + cursor->value = s; // Save the offset. + + PUGI__POPNODE(); // Pop since this is a standalone. + } + + s = strconv_pcdata(s); + + if (!*s) break; + } + else + { + PUGI__SCANFOR(*s == '<'); // '...<' + if (!*s) break; + + ++s; + } + + // We're after '<' + goto LOC_TAG; + } + } + + // check that last tag is closed + if (cursor != root) PUGI__THROW_ERROR(status_end_element_mismatch, s); + + return s; + } + + #ifdef PUGIXML_WCHAR_MODE + static char_t* parse_skip_bom(char_t* s) + { + unsigned int bom = 0xfeff; + return (s[0] == static_cast(bom)) ? s + 1 : s; + } + #else + static char_t* parse_skip_bom(char_t* s) + { + return (s[0] == '\xef' && s[1] == '\xbb' && s[2] == '\xbf') ? s + 3 : s; + } + #endif + + static bool has_element_node_siblings(xml_node_struct* node) + { + while (node) + { + if (PUGI__NODETYPE(node) == node_element) return true; + + node = node->next_sibling; + } + + return false; + } + + static xml_parse_result parse(char_t* buffer, size_t length, xml_document_struct* xmldoc, xml_node_struct* root, unsigned int optmsk) + { + // early-out for empty documents + if (length == 0) + return make_parse_result(PUGI__OPTSET(parse_fragment) ? status_ok : status_no_document_element); + + // get last child of the root before parsing + xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c + 0 : 0; + + // create parser on stack + xml_parser parser(static_cast(xmldoc)); + + // save last character and make buffer zero-terminated (speeds up parsing) + char_t endch = buffer[length - 1]; + buffer[length - 1] = 0; + + // skip BOM to make sure it does not end up as part of parse output + char_t* buffer_data = parse_skip_bom(buffer); + + // perform actual parsing + parser.parse_tree(buffer_data, root, optmsk, endch); + + xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0); + assert(result.offset >= 0 && static_cast(result.offset) <= length); + + if (result) + { + // since we removed last character, we have to handle the only possible false positive (stray <) + if (endch == '<') + return make_parse_result(status_unrecognized_tag, length - 1); + + // check if there are any element nodes parsed + xml_node_struct* first_root_child_parsed = last_root_child ? last_root_child->next_sibling + 0 : root->first_child+ 0; + + if (!PUGI__OPTSET(parse_fragment) && !has_element_node_siblings(first_root_child_parsed)) + return make_parse_result(status_no_document_element, length - 1); + } + else + { + // roll back offset if it occurs on a null terminator in the source buffer + if (result.offset > 0 && static_cast(result.offset) == length - 1 && endch == 0) + result.offset--; + } + + return result; + } + }; + + // Output facilities + PUGI__FN xml_encoding get_write_native_encoding() + { + #ifdef PUGIXML_WCHAR_MODE + return get_wchar_encoding(); + #else + return encoding_utf8; + #endif + } + + PUGI__FN xml_encoding get_write_encoding(xml_encoding encoding) + { + // replace wchar encoding with utf implementation + if (encoding == encoding_wchar) return get_wchar_encoding(); + + // replace utf16 encoding with utf16 with specific endianness + if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + // replace utf32 encoding with utf32 with specific endianness + if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + // only do autodetection if no explicit encoding is requested + if (encoding != encoding_auto) return encoding; + + // assume utf8 encoding + return encoding_utf8; + } + + template PUGI__FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T) + { + PUGI__STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type)); + + typename T::value_type end = D::process(reinterpret_cast(data), length, dest, T()); + + return static_cast(end - dest) * sizeof(*dest); + } + + template PUGI__FN size_t convert_buffer_output_generic(typename T::value_type dest, const char_t* data, size_t length, D, T, bool opt_swap) + { + PUGI__STATIC_ASSERT(sizeof(char_t) == sizeof(typename D::type)); + + typename T::value_type end = D::process(reinterpret_cast(data), length, dest, T()); + + if (opt_swap) + { + for (typename T::value_type i = dest; i != end; ++i) + *i = endian_swap(*i); + } + + return static_cast(end - dest) * sizeof(*dest); + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN size_t get_valid_length(const char_t* data, size_t length) + { + if (length < 1) return 0; + + // discard last character if it's the lead of a surrogate pair + return (sizeof(wchar_t) == 2 && static_cast(static_cast(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length; + } + + PUGI__FN size_t convert_buffer_output(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) + { + // only endian-swapping is required + if (need_endian_swap_utf(encoding, get_wchar_encoding())) + { + convert_wchar_endian_swap(r_char, data, length); + + return length * sizeof(char_t); + } + + // convert to utf8 + if (encoding == encoding_utf8) + return convert_buffer_output_generic(r_u8, data, length, wchar_decoder(), utf8_writer()); + + // convert to utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + return convert_buffer_output_generic(r_u16, data, length, wchar_decoder(), utf16_writer(), native_encoding != encoding); + } + + // convert to utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return convert_buffer_output_generic(r_u32, data, length, wchar_decoder(), utf32_writer(), native_encoding != encoding); + } + + // convert to latin1 + if (encoding == encoding_latin1) + return convert_buffer_output_generic(r_u8, data, length, wchar_decoder(), latin1_writer()); + + assert(false && "Invalid encoding"); // unreachable + return 0; + } +#else + PUGI__FN size_t get_valid_length(const char_t* data, size_t length) + { + if (length < 5) return 0; + + for (size_t i = 1; i <= 4; ++i) + { + uint8_t ch = static_cast(data[length - i]); + + // either a standalone character or a leading one + if ((ch & 0xc0) != 0x80) return length - i; + } + + // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk + return length; + } + + PUGI__FN size_t convert_buffer_output(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) + { + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + return convert_buffer_output_generic(r_u16, data, length, utf8_decoder(), utf16_writer(), native_encoding != encoding); + } + + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return convert_buffer_output_generic(r_u32, data, length, utf8_decoder(), utf32_writer(), native_encoding != encoding); + } + + if (encoding == encoding_latin1) + return convert_buffer_output_generic(r_u8, data, length, utf8_decoder(), latin1_writer()); + + assert(false && "Invalid encoding"); // unreachable + return 0; + } +#endif + + class xml_buffered_writer + { + xml_buffered_writer(const xml_buffered_writer&); + xml_buffered_writer& operator=(const xml_buffered_writer&); + + public: + xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding)) + { + PUGI__STATIC_ASSERT(bufcapacity >= 8); + } + + size_t flush() + { + flush(buffer, bufsize); + bufsize = 0; + return 0; + } + + void flush(const char_t* data, size_t size) + { + if (size == 0) return; + + // fast path, just write data + if (encoding == get_write_native_encoding()) + writer.write(data, size * sizeof(char_t)); + else + { + // convert chunk + size_t result = convert_buffer_output(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding); + assert(result <= sizeof(scratch)); + + // write data + writer.write(scratch.data_u8, result); + } + } + + void write_direct(const char_t* data, size_t length) + { + // flush the remaining buffer contents + flush(); + + // handle large chunks + if (length > bufcapacity) + { + if (encoding == get_write_native_encoding()) + { + // fast path, can just write data chunk + writer.write(data, length * sizeof(char_t)); + return; + } + + // need to convert in suitable chunks + while (length > bufcapacity) + { + // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer + // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary) + size_t chunk_size = get_valid_length(data, bufcapacity); + assert(chunk_size); + + // convert chunk and write + flush(data, chunk_size); + + // iterate + data += chunk_size; + length -= chunk_size; + } + + // small tail is copied below + bufsize = 0; + } + + memcpy(buffer + bufsize, data, length * sizeof(char_t)); + bufsize += length; + } + + void write_buffer(const char_t* data, size_t length) + { + size_t offset = bufsize; + + if (offset + length <= bufcapacity) + { + memcpy(buffer + offset, data, length * sizeof(char_t)); + bufsize = offset + length; + } + else + { + write_direct(data, length); + } + } + + void write_string(const char_t* data) + { + // write the part of the string that fits in the buffer + size_t offset = bufsize; + + while (*data && offset < bufcapacity) + buffer[offset++] = *data++; + + // write the rest + if (offset < bufcapacity) + { + bufsize = offset; + } + else + { + // backtrack a bit if we have split the codepoint + size_t length = offset - bufsize; + size_t extra = length - get_valid_length(data - length, length); + + bufsize = offset - extra; + + write_direct(data - extra, strlength(data) + extra); + } + } + + void write(char_t d0) + { + size_t offset = bufsize; + if (offset > bufcapacity - 1) offset = flush(); + + buffer[offset + 0] = d0; + bufsize = offset + 1; + } + + void write(char_t d0, char_t d1) + { + size_t offset = bufsize; + if (offset > bufcapacity - 2) offset = flush(); + + buffer[offset + 0] = d0; + buffer[offset + 1] = d1; + bufsize = offset + 2; + } + + void write(char_t d0, char_t d1, char_t d2) + { + size_t offset = bufsize; + if (offset > bufcapacity - 3) offset = flush(); + + buffer[offset + 0] = d0; + buffer[offset + 1] = d1; + buffer[offset + 2] = d2; + bufsize = offset + 3; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3) + { + size_t offset = bufsize; + if (offset > bufcapacity - 4) offset = flush(); + + buffer[offset + 0] = d0; + buffer[offset + 1] = d1; + buffer[offset + 2] = d2; + buffer[offset + 3] = d3; + bufsize = offset + 4; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4) + { + size_t offset = bufsize; + if (offset > bufcapacity - 5) offset = flush(); + + buffer[offset + 0] = d0; + buffer[offset + 1] = d1; + buffer[offset + 2] = d2; + buffer[offset + 3] = d3; + buffer[offset + 4] = d4; + bufsize = offset + 5; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5) + { + size_t offset = bufsize; + if (offset > bufcapacity - 6) offset = flush(); + + buffer[offset + 0] = d0; + buffer[offset + 1] = d1; + buffer[offset + 2] = d2; + buffer[offset + 3] = d3; + buffer[offset + 4] = d4; + buffer[offset + 5] = d5; + bufsize = offset + 6; + } + + // utf8 maximum expansion: x4 (-> utf32) + // utf16 maximum expansion: x2 (-> utf32) + // utf32 maximum expansion: x1 + enum + { + bufcapacitybytes = + #ifdef PUGIXML_MEMORY_OUTPUT_STACK + PUGIXML_MEMORY_OUTPUT_STACK + #else + 10240 + #endif + , + bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4) + }; + + char_t buffer[bufcapacity]; + + union + { + uint8_t data_u8[4 * bufcapacity]; + uint16_t data_u16[2 * bufcapacity]; + uint32_t data_u32[bufcapacity]; + char_t data_char[bufcapacity]; + } scratch; + + xml_writer& writer; + size_t bufsize; + xml_encoding encoding; + }; + + PUGI__FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) + { + while (*s) + { + const char_t* prev = s; + + // While *s is a usual symbol + PUGI__SCANWHILE_UNROLL(!PUGI__IS_CHARTYPEX(ss, type)); + + writer.write_buffer(prev, static_cast(s - prev)); + + switch (*s) + { + case 0: break; + case '&': + writer.write('&', 'a', 'm', 'p', ';'); + ++s; + break; + case '<': + writer.write('&', 'l', 't', ';'); + ++s; + break; + case '>': + writer.write('&', 'g', 't', ';'); + ++s; + break; + case '"': + if (flags & format_attribute_single_quote) + writer.write('"'); + else + writer.write('&', 'q', 'u', 'o', 't', ';'); + ++s; + break; + case '\'': + if (flags & format_attribute_single_quote) + writer.write('&', 'a', 'p', 'o', 's', ';'); + else + writer.write('\''); + ++s; + break; + default: // s is not a usual symbol + { + unsigned int ch = static_cast(*s++); + assert(ch < 32); + + if (!(flags & format_skip_control_chars)) + writer.write('&', '#', static_cast((ch / 10) + '0'), static_cast((ch % 10) + '0'), ';'); + } + } + } + } + + PUGI__FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) + { + if (flags & format_no_escapes) + writer.write_string(s); + else + text_output_escaped(writer, s, type, flags); + } + + PUGI__FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) + { + do + { + writer.write('<', '!', '[', 'C', 'D'); + writer.write('A', 'T', 'A', '['); + + const char_t* prev = s; + + // look for ]]> sequence - we can't output it as is since it terminates CDATA + while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) ++s; + + // skip ]] if we stopped at ]]>, > will go to the next CDATA section + if (*s) s += 2; + + writer.write_buffer(prev, static_cast(s - prev)); + + writer.write(']', ']', '>'); + } + while (*s); + } + + PUGI__FN void text_output_indent(xml_buffered_writer& writer, const char_t* indent, size_t indent_length, unsigned int depth) + { + switch (indent_length) + { + case 1: + { + for (unsigned int i = 0; i < depth; ++i) + writer.write(indent[0]); + break; + } + + case 2: + { + for (unsigned int i = 0; i < depth; ++i) + writer.write(indent[0], indent[1]); + break; + } + + case 3: + { + for (unsigned int i = 0; i < depth; ++i) + writer.write(indent[0], indent[1], indent[2]); + break; + } + + case 4: + { + for (unsigned int i = 0; i < depth; ++i) + writer.write(indent[0], indent[1], indent[2], indent[3]); + break; + } + + default: + { + for (unsigned int i = 0; i < depth; ++i) + writer.write_buffer(indent, indent_length); + } + } + } + + PUGI__FN void node_output_comment(xml_buffered_writer& writer, const char_t* s) + { + writer.write('<', '!', '-', '-'); + + while (*s) + { + const char_t* prev = s; + + // look for -\0 or -- sequence - we can't output it since -- is illegal in comment body + while (*s && !(s[0] == '-' && (s[1] == '-' || s[1] == 0))) ++s; + + writer.write_buffer(prev, static_cast(s - prev)); + + if (*s) + { + assert(*s == '-'); + + writer.write('-', ' '); + ++s; + } + } + + writer.write('-', '-', '>'); + } + + PUGI__FN void node_output_pi_value(xml_buffered_writer& writer, const char_t* s) + { + while (*s) + { + const char_t* prev = s; + + // look for ?> sequence - we can't output it since ?> terminates PI + while (*s && !(s[0] == '?' && s[1] == '>')) ++s; + + writer.write_buffer(prev, static_cast(s - prev)); + + if (*s) + { + assert(s[0] == '?' && s[1] == '>'); + + writer.write('?', ' ', '>'); + s += 2; + } + } + } + + PUGI__FN void node_output_attributes(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + const char_t enquotation_char = (flags & format_attribute_single_quote) ? '\'' : '"'; + + for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute) + { + if ((flags & (format_indent_attributes | format_raw)) == format_indent_attributes) + { + writer.write('\n'); + + text_output_indent(writer, indent, indent_length, depth + 1); + } + else + { + writer.write(' '); + } + + writer.write_string(a->name ? a->name + 0 : default_name); + writer.write('=', enquotation_char); + + if (a->value) + text_output(writer, a->value, ctx_special_attr, flags); + + writer.write(enquotation_char); + } + } + + PUGI__FN bool node_output_start(xml_buffered_writer& writer, xml_node_struct* node, const char_t* indent, size_t indent_length, unsigned int flags, unsigned int depth) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + const char_t* name = node->name ? node->name + 0 : default_name; + + writer.write('<'); + writer.write_string(name); + + if (node->first_attribute) + node_output_attributes(writer, node, indent, indent_length, flags, depth); + + // element nodes can have value if parse_embed_pcdata was used + if (!node->value) + { + if (!node->first_child) + { + if (flags & format_no_empty_element_tags) + { + writer.write('>', '<', '/'); + writer.write_string(name); + writer.write('>'); + + return false; + } + else + { + if ((flags & format_raw) == 0) + writer.write(' '); + + writer.write('/', '>'); + + return false; + } + } + else + { + writer.write('>'); + + return true; + } + } + else + { + writer.write('>'); + + text_output(writer, node->value, ctx_special_pcdata, flags); + + if (!node->first_child) + { + writer.write('<', '/'); + writer.write_string(name); + writer.write('>'); + + return false; + } + else + { + return true; + } + } + } + + PUGI__FN void node_output_end(xml_buffered_writer& writer, xml_node_struct* node) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + const char_t* name = node->name ? node->name + 0 : default_name; + + writer.write('<', '/'); + writer.write_string(name); + writer.write('>'); + } + + PUGI__FN void node_output_simple(xml_buffered_writer& writer, xml_node_struct* node, unsigned int flags) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + + switch (PUGI__NODETYPE(node)) + { + case node_pcdata: + text_output(writer, node->value ? node->value + 0 : PUGIXML_TEXT(""), ctx_special_pcdata, flags); + break; + + case node_cdata: + text_output_cdata(writer, node->value ? node->value + 0 : PUGIXML_TEXT("")); + break; + + case node_comment: + node_output_comment(writer, node->value ? node->value + 0 : PUGIXML_TEXT("")); + break; + + case node_pi: + writer.write('<', '?'); + writer.write_string(node->name ? node->name + 0 : default_name); + + if (node->value) + { + writer.write(' '); + node_output_pi_value(writer, node->value); + } + + writer.write('?', '>'); + break; + + case node_declaration: + writer.write('<', '?'); + writer.write_string(node->name ? node->name + 0 : default_name); + node_output_attributes(writer, node, PUGIXML_TEXT(""), 0, flags | format_raw, 0); + writer.write('?', '>'); + break; + + case node_doctype: + writer.write('<', '!', 'D', 'O', 'C'); + writer.write('T', 'Y', 'P', 'E'); + + if (node->value) + { + writer.write(' '); + writer.write_string(node->value); + } + + writer.write('>'); + break; + + default: + assert(false && "Invalid node type"); // unreachable + } + } + + enum indent_flags_t + { + indent_newline = 1, + indent_indent = 2 + }; + + PUGI__FN void node_output(xml_buffered_writer& writer, xml_node_struct* root, const char_t* indent, unsigned int flags, unsigned int depth) + { + size_t indent_length = ((flags & (format_indent | format_indent_attributes)) && (flags & format_raw) == 0) ? strlength(indent) : 0; + unsigned int indent_flags = indent_indent; + + xml_node_struct* node = root; + + do + { + assert(node); + + // begin writing current node + if (PUGI__NODETYPE(node) == node_pcdata || PUGI__NODETYPE(node) == node_cdata) + { + node_output_simple(writer, node, flags); + + indent_flags = 0; + } + else + { + if ((indent_flags & indent_newline) && (flags & format_raw) == 0) + writer.write('\n'); + + if ((indent_flags & indent_indent) && indent_length) + text_output_indent(writer, indent, indent_length, depth); + + if (PUGI__NODETYPE(node) == node_element) + { + indent_flags = indent_newline | indent_indent; + + if (node_output_start(writer, node, indent, indent_length, flags, depth)) + { + // element nodes can have value if parse_embed_pcdata was used + if (node->value) + indent_flags = 0; + + node = node->first_child; + depth++; + continue; + } + } + else if (PUGI__NODETYPE(node) == node_document) + { + indent_flags = indent_indent; + + if (node->first_child) + { + node = node->first_child; + continue; + } + } + else + { + node_output_simple(writer, node, flags); + + indent_flags = indent_newline | indent_indent; + } + } + + // continue to the next node + while (node != root) + { + if (node->next_sibling) + { + node = node->next_sibling; + break; + } + + node = node->parent; + + // write closing node + if (PUGI__NODETYPE(node) == node_element) + { + depth--; + + if ((indent_flags & indent_newline) && (flags & format_raw) == 0) + writer.write('\n'); + + if ((indent_flags & indent_indent) && indent_length) + text_output_indent(writer, indent, indent_length, depth); + + node_output_end(writer, node); + + indent_flags = indent_newline | indent_indent; + } + } + } + while (node != root); + + if ((indent_flags & indent_newline) && (flags & format_raw) == 0) + writer.write('\n'); + } + + PUGI__FN bool has_declaration(xml_node_struct* node) + { + for (xml_node_struct* child = node->first_child; child; child = child->next_sibling) + { + xml_node_type type = PUGI__NODETYPE(child); + + if (type == node_declaration) return true; + if (type == node_element) return false; + } + + return false; + } + + PUGI__FN bool is_attribute_of(xml_attribute_struct* attr, xml_node_struct* node) + { + for (xml_attribute_struct* a = node->first_attribute; a; a = a->next_attribute) + if (a == attr) + return true; + + return false; + } + + PUGI__FN bool allow_insert_attribute(xml_node_type parent) + { + return parent == node_element || parent == node_declaration; + } + + PUGI__FN bool allow_insert_child(xml_node_type parent, xml_node_type child) + { + if (parent != node_document && parent != node_element) return false; + if (child == node_document || child == node_null) return false; + if (parent != node_document && (child == node_declaration || child == node_doctype)) return false; + + return true; + } + + PUGI__FN bool allow_move(xml_node parent, xml_node child) + { + // check that child can be a child of parent + if (!allow_insert_child(parent.type(), child.type())) + return false; + + // check that node is not moved between documents + if (parent.root() != child.root()) + return false; + + // check that new parent is not in the child subtree + xml_node cur = parent; + + while (cur) + { + if (cur == child) + return false; + + cur = cur.parent(); + } + + return true; + } + + template + PUGI__FN void node_copy_string(String& dest, Header& header, uintptr_t header_mask, char_t* source, Header& source_header, xml_allocator* alloc) + { + assert(!dest && (header & header_mask) == 0); + + if (source) + { + if (alloc && (source_header & header_mask) == 0) + { + dest = source; + + // since strcpy_insitu can reuse document buffer memory we need to mark both source and dest as shared + header |= xml_memory_page_contents_shared_mask; + source_header |= xml_memory_page_contents_shared_mask; + } + else + strcpy_insitu(dest, header, header_mask, source, strlength(source)); + } + } + + PUGI__FN void node_copy_contents(xml_node_struct* dn, xml_node_struct* sn, xml_allocator* shared_alloc) + { + node_copy_string(dn->name, dn->header, xml_memory_page_name_allocated_mask, sn->name, sn->header, shared_alloc); + node_copy_string(dn->value, dn->header, xml_memory_page_value_allocated_mask, sn->value, sn->header, shared_alloc); + + for (xml_attribute_struct* sa = sn->first_attribute; sa; sa = sa->next_attribute) + { + xml_attribute_struct* da = append_new_attribute(dn, get_allocator(dn)); + + if (da) + { + node_copy_string(da->name, da->header, xml_memory_page_name_allocated_mask, sa->name, sa->header, shared_alloc); + node_copy_string(da->value, da->header, xml_memory_page_value_allocated_mask, sa->value, sa->header, shared_alloc); + } + } + } + + PUGI__FN void node_copy_tree(xml_node_struct* dn, xml_node_struct* sn) + { + xml_allocator& alloc = get_allocator(dn); + xml_allocator* shared_alloc = (&alloc == &get_allocator(sn)) ? &alloc : 0; + + node_copy_contents(dn, sn, shared_alloc); + + xml_node_struct* dit = dn; + xml_node_struct* sit = sn->first_child; + + while (sit && sit != sn) + { + // loop invariant: dit is inside the subtree rooted at dn + assert(dit); + + // when a tree is copied into one of the descendants, we need to skip that subtree to avoid an infinite loop + if (sit != dn) + { + xml_node_struct* copy = append_new_node(dit, alloc, PUGI__NODETYPE(sit)); + + if (copy) + { + node_copy_contents(copy, sit, shared_alloc); + + if (sit->first_child) + { + dit = copy; + sit = sit->first_child; + continue; + } + } + } + + // continue to the next node + do + { + if (sit->next_sibling) + { + sit = sit->next_sibling; + break; + } + + sit = sit->parent; + dit = dit->parent; + + // loop invariant: dit is inside the subtree rooted at dn while sit is inside sn + assert(sit == sn || dit); + } + while (sit != sn); + } + + assert(!sit || dit == dn->parent); + } + + PUGI__FN void node_copy_attribute(xml_attribute_struct* da, xml_attribute_struct* sa) + { + xml_allocator& alloc = get_allocator(da); + xml_allocator* shared_alloc = (&alloc == &get_allocator(sa)) ? &alloc : 0; + + node_copy_string(da->name, da->header, xml_memory_page_name_allocated_mask, sa->name, sa->header, shared_alloc); + node_copy_string(da->value, da->header, xml_memory_page_value_allocated_mask, sa->value, sa->header, shared_alloc); + } + + inline bool is_text_node(xml_node_struct* node) + { + xml_node_type type = PUGI__NODETYPE(node); + + return type == node_pcdata || type == node_cdata; + } + + // get value with conversion functions + template PUGI__FN PUGI__UNSIGNED_OVERFLOW U string_to_integer(const char_t* value, U minv, U maxv) + { + U result = 0; + const char_t* s = value; + + while (PUGI__IS_CHARTYPE(*s, ct_space)) + s++; + + bool negative = (*s == '-'); + + s += (*s == '+' || *s == '-'); + + bool overflow = false; + + if (s[0] == '0' && (s[1] | ' ') == 'x') + { + s += 2; + + // since overflow detection relies on length of the sequence skip leading zeros + while (*s == '0') + s++; + + const char_t* start = s; + + for (;;) + { + if (static_cast(*s - '0') < 10) + result = result * 16 + (*s - '0'); + else if (static_cast((*s | ' ') - 'a') < 6) + result = result * 16 + ((*s | ' ') - 'a' + 10); + else + break; + + s++; + } + + size_t digits = static_cast(s - start); + + overflow = digits > sizeof(U) * 2; + } + else + { + // since overflow detection relies on length of the sequence skip leading zeros + while (*s == '0') + s++; + + const char_t* start = s; + + for (;;) + { + if (static_cast(*s - '0') < 10) + result = result * 10 + (*s - '0'); + else + break; + + s++; + } + + size_t digits = static_cast(s - start); + + PUGI__STATIC_ASSERT(sizeof(U) == 8 || sizeof(U) == 4 || sizeof(U) == 2); + + const size_t max_digits10 = sizeof(U) == 8 ? 20 : sizeof(U) == 4 ? 10 : 5; + const char_t max_lead = sizeof(U) == 8 ? '1' : sizeof(U) == 4 ? '4' : '6'; + const size_t high_bit = sizeof(U) * 8 - 1; + + overflow = digits >= max_digits10 && !(digits == max_digits10 && (*start < max_lead || (*start == max_lead && result >> high_bit))); + } + + if (negative) + { + // Workaround for crayc++ CC-3059: Expected no overflow in routine. + #ifdef _CRAYC + return (overflow || result > ~minv + 1) ? minv : ~result + 1; + #else + return (overflow || result > 0 - minv) ? minv : 0 - result; + #endif + } + else + return (overflow || result > maxv) ? maxv : result; + } + + PUGI__FN int get_value_int(const char_t* value) + { + return string_to_integer(value, static_cast(INT_MIN), INT_MAX); + } + + PUGI__FN unsigned int get_value_uint(const char_t* value) + { + return string_to_integer(value, 0, UINT_MAX); + } + + PUGI__FN double get_value_double(const char_t* value) + { + #ifdef PUGIXML_WCHAR_MODE + return wcstod(value, 0); + #else + return strtod(value, 0); + #endif + } + + PUGI__FN float get_value_float(const char_t* value) + { + #ifdef PUGIXML_WCHAR_MODE + return static_cast(wcstod(value, 0)); + #else + return static_cast(strtod(value, 0)); + #endif + } + + PUGI__FN bool get_value_bool(const char_t* value) + { + // only look at first char + char_t first = *value; + + // 1*, t* (true), T* (True), y* (yes), Y* (YES) + return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y'); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long get_value_llong(const char_t* value) + { + return string_to_integer(value, static_cast(LLONG_MIN), LLONG_MAX); + } + + PUGI__FN unsigned long long get_value_ullong(const char_t* value) + { + return string_to_integer(value, 0, ULLONG_MAX); + } +#endif + + template PUGI__FN PUGI__UNSIGNED_OVERFLOW char_t* integer_to_string(char_t* begin, char_t* end, U value, bool negative) + { + char_t* result = end - 1; + U rest = negative ? 0 - value : value; + + do + { + *result-- = static_cast('0' + (rest % 10)); + rest /= 10; + } + while (rest); + + assert(result >= begin); + (void)begin; + + *result = '-'; + + return result + !negative; + } + + // set value with conversion functions + template + PUGI__FN bool set_value_ascii(String& dest, Header& header, uintptr_t header_mask, char* buf) + { + #ifdef PUGIXML_WCHAR_MODE + char_t wbuf[128]; + assert(strlen(buf) < sizeof(wbuf) / sizeof(wbuf[0])); + + size_t offset = 0; + for (; buf[offset]; ++offset) wbuf[offset] = buf[offset]; + + return strcpy_insitu(dest, header, header_mask, wbuf, offset); + #else + return strcpy_insitu(dest, header, header_mask, buf, strlen(buf)); + #endif + } + + template + PUGI__FN bool set_value_integer(String& dest, Header& header, uintptr_t header_mask, U value, bool negative) + { + char_t buf[64]; + char_t* end = buf + sizeof(buf) / sizeof(buf[0]); + char_t* begin = integer_to_string(buf, end, value, negative); + + return strcpy_insitu(dest, header, header_mask, begin, end - begin); + } + + template + PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, float value, int precision) + { + char buf[128]; + PUGI__SNPRINTF(buf, "%.*g", precision, double(value)); + + return set_value_ascii(dest, header, header_mask, buf); + } + + template + PUGI__FN bool set_value_convert(String& dest, Header& header, uintptr_t header_mask, double value, int precision) + { + char buf[128]; + PUGI__SNPRINTF(buf, "%.*g", precision, value); + + return set_value_ascii(dest, header, header_mask, buf); + } + + template + PUGI__FN bool set_value_bool(String& dest, Header& header, uintptr_t header_mask, bool value) + { + return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false"), value ? 4 : 5); + } + + PUGI__FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer) + { + // check input buffer + if (!contents && size) return make_parse_result(status_io_error); + + // get actual encoding + xml_encoding buffer_encoding = impl::get_buffer_encoding(encoding, contents, size); + + // get private buffer + char_t* buffer = 0; + size_t length = 0; + + // coverity[var_deref_model] + if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory); + + // delete original buffer if we performed a conversion + if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents); + + // grab onto buffer if it's our buffer, user is responsible for deallocating contents himself + if (own || buffer != contents) *out_buffer = buffer; + + // store buffer for offset_debug + doc->buffer = buffer; + + // parse + xml_parse_result res = impl::xml_parser::parse(buffer, length, doc, root, options); + + // remember encoding + res.encoding = buffer_encoding; + + return res; + } + + // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick + PUGI__FN xml_parse_status get_file_size(FILE* file, size_t& out_result) + { + #if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 + // there are 64-bit versions of fseek/ftell, let's use them + typedef __int64 length_type; + + _fseeki64(file, 0, SEEK_END); + length_type length = _ftelli64(file); + _fseeki64(file, 0, SEEK_SET); + #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR)) + // there are 64-bit versions of fseek/ftell, let's use them + typedef off64_t length_type; + + fseeko64(file, 0, SEEK_END); + length_type length = ftello64(file); + fseeko64(file, 0, SEEK_SET); + #else + // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway. + typedef long length_type; + + fseek(file, 0, SEEK_END); + length_type length = ftell(file); + fseek(file, 0, SEEK_SET); + #endif + + // check for I/O errors + if (length < 0) return status_io_error; + + // check for overflow + size_t result = static_cast(length); + + if (static_cast(result) != length) return status_out_of_memory; + + // finalize + out_result = result; + + return status_ok; + } + + // This function assumes that buffer has extra sizeof(char_t) writable bytes after size + PUGI__FN size_t zero_terminate_buffer(void* buffer, size_t size, xml_encoding encoding) + { + // We only need to zero-terminate if encoding conversion does not do it for us + #ifdef PUGIXML_WCHAR_MODE + xml_encoding wchar_encoding = get_wchar_encoding(); + + if (encoding == wchar_encoding || need_endian_swap_utf(encoding, wchar_encoding)) + { + size_t length = size / sizeof(char_t); + + static_cast(buffer)[length] = 0; + return (length + 1) * sizeof(char_t); + } + #else + if (encoding == encoding_utf8) + { + static_cast(buffer)[size] = 0; + return size + 1; + } + #endif + + return size; + } + + PUGI__FN xml_parse_result load_file_impl(xml_document_struct* doc, FILE* file, unsigned int options, xml_encoding encoding, char_t** out_buffer) + { + if (!file) return make_parse_result(status_file_not_found); + + // get file size (can result in I/O errors) + size_t size = 0; + xml_parse_status size_status = get_file_size(file, size); + if (size_status != status_ok) return make_parse_result(size_status); + + size_t max_suffix_size = sizeof(char_t); + + // allocate buffer for the whole file + char* contents = static_cast(xml_memory::allocate(size + max_suffix_size)); + if (!contents) return make_parse_result(status_out_of_memory); + + // read file in memory + size_t read_size = fread(contents, 1, size, file); + + if (read_size != size) + { + xml_memory::deallocate(contents); + return make_parse_result(status_io_error); + } + + xml_encoding real_encoding = get_buffer_encoding(encoding, contents, size); + + return load_buffer_impl(doc, doc, contents, zero_terminate_buffer(contents, size, real_encoding), options, real_encoding, true, true, out_buffer); + } + + PUGI__FN void close_file(FILE* file) + { + fclose(file); + } + +#ifndef PUGIXML_NO_STL + template struct xml_stream_chunk + { + static xml_stream_chunk* create() + { + void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); + if (!memory) return 0; + + return new (memory) xml_stream_chunk(); + } + + static void destroy(xml_stream_chunk* chunk) + { + // free chunk chain + while (chunk) + { + xml_stream_chunk* next_ = chunk->next; + + xml_memory::deallocate(chunk); + + chunk = next_; + } + } + + xml_stream_chunk(): next(0), size(0) + { + } + + xml_stream_chunk* next; + size_t size; + + T data[xml_memory_page_size / sizeof(T)]; + }; + + template PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) + { + auto_deleter > chunks(0, xml_stream_chunk::destroy); + + // read file to a chunk list + size_t total = 0; + xml_stream_chunk* last = 0; + + while (!stream.eof()) + { + // allocate new chunk + xml_stream_chunk* chunk = xml_stream_chunk::create(); + if (!chunk) return status_out_of_memory; + + // append chunk to list + if (last) last = last->next = chunk; + else chunks.data = last = chunk; + + // read data to chunk + stream.read(chunk->data, static_cast(sizeof(chunk->data) / sizeof(T))); + chunk->size = static_cast(stream.gcount()) * sizeof(T); + + // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors + if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; + + // guard against huge files (chunk size is small enough to make this overflow check work) + if (total + chunk->size < total) return status_out_of_memory; + total += chunk->size; + } + + size_t max_suffix_size = sizeof(char_t); + + // copy chunk list to a contiguous buffer + char* buffer = static_cast(xml_memory::allocate(total + max_suffix_size)); + if (!buffer) return status_out_of_memory; + + char* write = buffer; + + for (xml_stream_chunk* chunk = chunks.data; chunk; chunk = chunk->next) + { + assert(write + chunk->size <= buffer + total); + memcpy(write, chunk->data, chunk->size); + write += chunk->size; + } + + assert(write == buffer + total); + + // return buffer + *out_buffer = buffer; + *out_size = total; + + return status_ok; + } + + template PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) + { + // get length of remaining data in stream + typename std::basic_istream::pos_type pos = stream.tellg(); + stream.seekg(0, std::ios::end); + std::streamoff length = stream.tellg() - pos; + stream.seekg(pos); + + if (stream.fail() || pos < 0) return status_io_error; + + // guard against huge files + size_t read_length = static_cast(length); + + if (static_cast(read_length) != length || length < 0) return status_out_of_memory; + + size_t max_suffix_size = sizeof(char_t); + + // read stream data into memory (guard against stream exceptions with buffer holder) + auto_deleter buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate); + if (!buffer.data) return status_out_of_memory; + + stream.read(static_cast(buffer.data), static_cast(read_length)); + + // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors + if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; + + // return buffer + size_t actual_length = static_cast(stream.gcount()); + assert(actual_length <= read_length); + + *out_buffer = buffer.release(); + *out_size = actual_length * sizeof(T); + + return status_ok; + } + + template PUGI__FN xml_parse_result load_stream_impl(xml_document_struct* doc, std::basic_istream& stream, unsigned int options, xml_encoding encoding, char_t** out_buffer) + { + void* buffer = 0; + size_t size = 0; + xml_parse_status status = status_ok; + + // if stream has an error bit set, bail out (otherwise tellg() can fail and we'll clear error bits) + if (stream.fail()) return make_parse_result(status_io_error); + + // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory) + if (stream.tellg() < 0) + { + stream.clear(); // clear error flags that could be set by a failing tellg + status = load_stream_data_noseek(stream, &buffer, &size); + } + else + status = load_stream_data_seek(stream, &buffer, &size); + + if (status != status_ok) return make_parse_result(status); + + xml_encoding real_encoding = get_buffer_encoding(encoding, buffer, size); + + return load_buffer_impl(doc, doc, buffer, zero_terminate_buffer(buffer, size, real_encoding), options, real_encoding, true, true, out_buffer); + } +#endif + +#if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && (!defined(__STRICT_ANSI__) || defined(__MINGW64_VERSION_MAJOR))) + PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) + { +#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 + FILE* file = 0; + return _wfopen_s(&file, path, mode) == 0 ? file : 0; +#else + return _wfopen(path, mode); +#endif + } +#else + PUGI__FN char* convert_path_heap(const wchar_t* str) + { + assert(str); + + // first pass: get length in utf8 characters + size_t length = strlength_wide(str); + size_t size = as_utf8_begin(str, length); + + // allocate resulting string + char* result = static_cast(xml_memory::allocate(size + 1)); + if (!result) return 0; + + // second pass: convert to utf8 + as_utf8_end(result, size, str, length); + + // zero-terminate + result[size] = 0; + + return result; + } + + PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) + { + // there is no standard function to open wide paths, so our best bet is to try utf8 path + char* path_utf8 = convert_path_heap(path); + if (!path_utf8) return 0; + + // convert mode to ASCII (we mirror _wfopen interface) + char mode_ascii[4] = {0}; + for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast(mode[i]); + + // try to open the utf8 path + FILE* result = fopen(path_utf8, mode_ascii); + + // free dummy buffer + xml_memory::deallocate(path_utf8); + + return result; + } +#endif + + PUGI__FN FILE* open_file(const char* path, const char* mode) + { +#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 + FILE* file = 0; + return fopen_s(&file, path, mode) == 0 ? file : 0; +#else + return fopen(path, mode); +#endif + } + + PUGI__FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) + { + if (!file) return false; + + xml_writer_file writer(file); + doc.save(writer, indent, flags, encoding); + + return ferror(file) == 0; + } + + struct name_null_sentry + { + xml_node_struct* node; + char_t* name; + + name_null_sentry(xml_node_struct* node_): node(node_), name(node_->name) + { + node->name = 0; + } + + ~name_null_sentry() + { + node->name = name; + } + }; +PUGI__NS_END + +namespace pugi +{ + PUGI__FN xml_writer_file::xml_writer_file(void* file_): file(file_) + { + } + + PUGI__FN void xml_writer_file::write(const void* data, size_t size) + { + size_t result = fwrite(data, 1, size, static_cast(file)); + (void)!result; // unfortunately we can't do proper error handling here + } + +#ifndef PUGIXML_NO_STL + PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(&stream), wide_stream(0) + { + } + + PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(0), wide_stream(&stream) + { + } + + PUGI__FN void xml_writer_stream::write(const void* data, size_t size) + { + if (narrow_stream) + { + assert(!wide_stream); + narrow_stream->write(reinterpret_cast(data), static_cast(size)); + } + else + { + assert(wide_stream); + assert(size % sizeof(wchar_t) == 0); + + wide_stream->write(reinterpret_cast(data), static_cast(size / sizeof(wchar_t))); + } + } +#endif + + PUGI__FN xml_tree_walker::xml_tree_walker(): _depth(0) + { + } + + PUGI__FN xml_tree_walker::~xml_tree_walker() + { + } + + PUGI__FN int xml_tree_walker::depth() const + { + return _depth; + } + + PUGI__FN bool xml_tree_walker::begin(xml_node&) + { + return true; + } + + PUGI__FN bool xml_tree_walker::end(xml_node&) + { + return true; + } + + PUGI__FN xml_attribute::xml_attribute(): _attr(0) + { + } + + PUGI__FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr) + { + } + + PUGI__FN static void unspecified_bool_xml_attribute(xml_attribute***) + { + } + + PUGI__FN xml_attribute::operator xml_attribute::unspecified_bool_type() const + { + return _attr ? unspecified_bool_xml_attribute : 0; + } + + PUGI__FN bool xml_attribute::operator!() const + { + return !_attr; + } + + PUGI__FN bool xml_attribute::operator==(const xml_attribute& r) const + { + return (_attr == r._attr); + } + + PUGI__FN bool xml_attribute::operator!=(const xml_attribute& r) const + { + return (_attr != r._attr); + } + + PUGI__FN bool xml_attribute::operator<(const xml_attribute& r) const + { + return (_attr < r._attr); + } + + PUGI__FN bool xml_attribute::operator>(const xml_attribute& r) const + { + return (_attr > r._attr); + } + + PUGI__FN bool xml_attribute::operator<=(const xml_attribute& r) const + { + return (_attr <= r._attr); + } + + PUGI__FN bool xml_attribute::operator>=(const xml_attribute& r) const + { + return (_attr >= r._attr); + } + + PUGI__FN xml_attribute xml_attribute::next_attribute() const + { + return _attr ? xml_attribute(_attr->next_attribute) : xml_attribute(); + } + + PUGI__FN xml_attribute xml_attribute::previous_attribute() const + { + return _attr && _attr->prev_attribute_c->next_attribute ? xml_attribute(_attr->prev_attribute_c) : xml_attribute(); + } + + PUGI__FN const char_t* xml_attribute::as_string(const char_t* def) const + { + return (_attr && _attr->value) ? _attr->value + 0 : def; + } + + PUGI__FN int xml_attribute::as_int(int def) const + { + return (_attr && _attr->value) ? impl::get_value_int(_attr->value) : def; + } + + PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const + { + return (_attr && _attr->value) ? impl::get_value_uint(_attr->value) : def; + } + + PUGI__FN double xml_attribute::as_double(double def) const + { + return (_attr && _attr->value) ? impl::get_value_double(_attr->value) : def; + } + + PUGI__FN float xml_attribute::as_float(float def) const + { + return (_attr && _attr->value) ? impl::get_value_float(_attr->value) : def; + } + + PUGI__FN bool xml_attribute::as_bool(bool def) const + { + return (_attr && _attr->value) ? impl::get_value_bool(_attr->value) : def; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long xml_attribute::as_llong(long long def) const + { + return (_attr && _attr->value) ? impl::get_value_llong(_attr->value) : def; + } + + PUGI__FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const + { + return (_attr && _attr->value) ? impl::get_value_ullong(_attr->value) : def; + } +#endif + + PUGI__FN bool xml_attribute::empty() const + { + return !_attr; + } + + PUGI__FN const char_t* xml_attribute::name() const + { + return (_attr && _attr->name) ? _attr->name + 0 : PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_attribute::value() const + { + return (_attr && _attr->value) ? _attr->value + 0 : PUGIXML_TEXT(""); + } + + PUGI__FN size_t xml_attribute::hash_value() const + { + return static_cast(reinterpret_cast(_attr) / sizeof(xml_attribute_struct)); + } + + PUGI__FN xml_attribute_struct* xml_attribute::internal_object() const + { + return _attr; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(const char_t* rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(int rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(unsigned int rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(long rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(unsigned long rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(double rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(float rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(bool rhs) + { + set_value(rhs); + return *this; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN xml_attribute& xml_attribute::operator=(long long rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(unsigned long long rhs) + { + set_value(rhs); + return *this; + } +#endif + + PUGI__FN bool xml_attribute::set_name(const char_t* rhs) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs, impl::strlength(rhs)); + } + + PUGI__FN bool xml_attribute::set_value(const char_t* rhs) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)); + } + + PUGI__FN bool xml_attribute::set_value(int rhs) + { + if (!_attr) return false; + + return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0); + } + + PUGI__FN bool xml_attribute::set_value(unsigned int rhs) + { + if (!_attr) return false; + + return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false); + } + + PUGI__FN bool xml_attribute::set_value(long rhs) + { + if (!_attr) return false; + + return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0); + } + + PUGI__FN bool xml_attribute::set_value(unsigned long rhs) + { + if (!_attr) return false; + + return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false); + } + + PUGI__FN bool xml_attribute::set_value(double rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, default_double_precision); + } + + PUGI__FN bool xml_attribute::set_value(double rhs, int precision) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, precision); + } + + PUGI__FN bool xml_attribute::set_value(float rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, default_float_precision); + } + + PUGI__FN bool xml_attribute::set_value(float rhs, int precision) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, precision); + } + + PUGI__FN bool xml_attribute::set_value(bool rhs) + { + if (!_attr) return false; + + return impl::set_value_bool(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool xml_attribute::set_value(long long rhs) + { + if (!_attr) return false; + + return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0); + } + + PUGI__FN bool xml_attribute::set_value(unsigned long long rhs) + { + if (!_attr) return false; + + return impl::set_value_integer(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs, false); + } +#endif + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_attribute& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_attribute& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_node::xml_node(): _root(0) + { + } + + PUGI__FN xml_node::xml_node(xml_node_struct* p): _root(p) + { + } + + PUGI__FN static void unspecified_bool_xml_node(xml_node***) + { + } + + PUGI__FN xml_node::operator xml_node::unspecified_bool_type() const + { + return _root ? unspecified_bool_xml_node : 0; + } + + PUGI__FN bool xml_node::operator!() const + { + return !_root; + } + + PUGI__FN xml_node::iterator xml_node::begin() const + { + return iterator(_root ? _root->first_child + 0 : 0, _root); + } + + PUGI__FN xml_node::iterator xml_node::end() const + { + return iterator(0, _root); + } + + PUGI__FN xml_node::attribute_iterator xml_node::attributes_begin() const + { + return attribute_iterator(_root ? _root->first_attribute + 0 : 0, _root); + } + + PUGI__FN xml_node::attribute_iterator xml_node::attributes_end() const + { + return attribute_iterator(0, _root); + } + + PUGI__FN xml_object_range xml_node::children() const + { + return xml_object_range(begin(), end()); + } + + PUGI__FN xml_object_range xml_node::children(const char_t* name_) const + { + return xml_object_range(xml_named_node_iterator(child(name_)._root, _root, name_), xml_named_node_iterator(0, _root, name_)); + } + + PUGI__FN xml_object_range xml_node::attributes() const + { + return xml_object_range(attributes_begin(), attributes_end()); + } + + PUGI__FN bool xml_node::operator==(const xml_node& r) const + { + return (_root == r._root); + } + + PUGI__FN bool xml_node::operator!=(const xml_node& r) const + { + return (_root != r._root); + } + + PUGI__FN bool xml_node::operator<(const xml_node& r) const + { + return (_root < r._root); + } + + PUGI__FN bool xml_node::operator>(const xml_node& r) const + { + return (_root > r._root); + } + + PUGI__FN bool xml_node::operator<=(const xml_node& r) const + { + return (_root <= r._root); + } + + PUGI__FN bool xml_node::operator>=(const xml_node& r) const + { + return (_root >= r._root); + } + + PUGI__FN bool xml_node::empty() const + { + return !_root; + } + + PUGI__FN const char_t* xml_node::name() const + { + return (_root && _root->name) ? _root->name + 0 : PUGIXML_TEXT(""); + } + + PUGI__FN xml_node_type xml_node::type() const + { + return _root ? PUGI__NODETYPE(_root) : node_null; + } + + PUGI__FN const char_t* xml_node::value() const + { + return (_root && _root->value) ? _root->value + 0 : PUGIXML_TEXT(""); + } + + PUGI__FN xml_node xml_node::child(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) + if (i->name && impl::strequal(name_, i->name)) + return xml_attribute(i); + + return xml_attribute(); + } + + PUGI__FN xml_node xml_node::next_sibling(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_node xml_node::next_sibling() const + { + return _root ? xml_node(_root->next_sibling) : xml_node(); + } + + PUGI__FN xml_node xml_node::previous_sibling(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_attribute xml_node::attribute(const char_t* name_, xml_attribute& hint_) const + { + xml_attribute_struct* hint = hint_._attr; + + // if hint is not an attribute of node, behavior is not defined + assert(!hint || (_root && impl::is_attribute_of(hint, _root))); + + if (!_root) return xml_attribute(); + + // optimistically search from hint up until the end + for (xml_attribute_struct* i = hint; i; i = i->next_attribute) + if (i->name && impl::strequal(name_, i->name)) + { + // update hint to maximize efficiency of searching for consecutive attributes + hint_._attr = i->next_attribute; + + return xml_attribute(i); + } + + // wrap around and search from the first attribute until the hint + // 'j' null pointer check is technically redundant, but it prevents a crash in case the assertion above fails + for (xml_attribute_struct* j = _root->first_attribute; j && j != hint; j = j->next_attribute) + if (j->name && impl::strequal(name_, j->name)) + { + // update hint to maximize efficiency of searching for consecutive attributes + hint_._attr = j->next_attribute; + + return xml_attribute(j); + } + + return xml_attribute(); + } + + PUGI__FN xml_node xml_node::previous_sibling() const + { + if (!_root) return xml_node(); + + if (_root->prev_sibling_c->next_sibling) return xml_node(_root->prev_sibling_c); + else return xml_node(); + } + + PUGI__FN xml_node xml_node::parent() const + { + return _root ? xml_node(_root->parent) : xml_node(); + } + + PUGI__FN xml_node xml_node::root() const + { + return _root ? xml_node(&impl::get_document(_root)) : xml_node(); + } + + PUGI__FN xml_text xml_node::text() const + { + return xml_text(_root); + } + + PUGI__FN const char_t* xml_node::child_value() const + { + if (!_root) return PUGIXML_TEXT(""); + + // element nodes can have value if parse_embed_pcdata was used + if (PUGI__NODETYPE(_root) == node_element && _root->value) + return _root->value; + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (impl::is_text_node(i) && i->value) + return i->value; + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const + { + return child(name_).child_value(); + } + + PUGI__FN xml_attribute xml_node::first_attribute() const + { + return _root ? xml_attribute(_root->first_attribute) : xml_attribute(); + } + + PUGI__FN xml_attribute xml_node::last_attribute() const + { + return _root && _root->first_attribute ? xml_attribute(_root->first_attribute->prev_attribute_c) : xml_attribute(); + } + + PUGI__FN xml_node xml_node::first_child() const + { + return _root ? xml_node(_root->first_child) : xml_node(); + } + + PUGI__FN xml_node xml_node::last_child() const + { + return _root && _root->first_child ? xml_node(_root->first_child->prev_sibling_c) : xml_node(); + } + + PUGI__FN bool xml_node::set_name(const char_t* rhs) + { + xml_node_type type_ = _root ? PUGI__NODETYPE(_root) : node_null; + + if (type_ != node_element && type_ != node_pi && type_ != node_declaration) + return false; + + return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs, impl::strlength(rhs)); + } + + PUGI__FN bool xml_node::set_value(const char_t* rhs) + { + xml_node_type type_ = _root ? PUGI__NODETYPE(_root) : node_null; + + if (type_ != node_pcdata && type_ != node_cdata && type_ != node_comment && type_ != node_pi && type_ != node_doctype) + return false; + + return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)); + } + + PUGI__FN xml_attribute xml_node::append_attribute(const char_t* name_) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::append_attribute(a._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::prepend_attribute(const char_t* name_) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::prepend_attribute(a._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::insert_attribute_after(a._attr, attr._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) + { + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::insert_attribute_before(a._attr, attr._attr, _root); + + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::append_copy(const xml_attribute& proto) + { + if (!proto) return xml_attribute(); + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::append_attribute(a._attr, _root); + impl::node_copy_attribute(a._attr, proto._attr); + + return a; + } + + PUGI__FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) + { + if (!proto) return xml_attribute(); + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::prepend_attribute(a._attr, _root); + impl::node_copy_attribute(a._attr, proto._attr); + + return a; + } + + PUGI__FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) + { + if (!proto) return xml_attribute(); + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::insert_attribute_after(a._attr, attr._attr, _root); + impl::node_copy_attribute(a._attr, proto._attr); + + return a; + } + + PUGI__FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) + { + if (!proto) return xml_attribute(); + if (!impl::allow_insert_attribute(type())) return xml_attribute(); + if (!attr || !impl::is_attribute_of(attr._attr, _root)) return xml_attribute(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(alloc)); + if (!a) return xml_attribute(); + + impl::insert_attribute_before(a._attr, attr._attr, _root); + impl::node_copy_attribute(a._attr, proto._attr); + + return a; + } + + PUGI__FN xml_node xml_node::append_child(xml_node_type type_) + { + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + xml_node n(impl::allocate_node(alloc, type_)); + if (!n) return xml_node(); + + impl::append_node(n._root, _root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::prepend_child(xml_node_type type_) + { + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + xml_node n(impl::allocate_node(alloc, type_)); + if (!n) return xml_node(); + + impl::prepend_node(n._root, _root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) + { + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + xml_node n(impl::allocate_node(alloc, type_)); + if (!n) return xml_node(); + + impl::insert_node_before(n._root, node._root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) + { + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + xml_node n(impl::allocate_node(alloc, type_)); + if (!n) return xml_node(); + + impl::insert_node_after(n._root, node._root); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::append_child(const char_t* name_) + { + xml_node result = append_child(node_element); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::prepend_child(const char_t* name_) + { + xml_node result = prepend_child(node_element); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) + { + xml_node result = insert_child_after(node_element, node); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) + { + xml_node result = insert_child_before(node_element, node); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::append_copy(const xml_node& proto) + { + xml_node_type type_ = proto.type(); + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + xml_node n(impl::allocate_node(alloc, type_)); + if (!n) return xml_node(); + + impl::append_node(n._root, _root); + impl::node_copy_tree(n._root, proto._root); + + return n; + } + + PUGI__FN xml_node xml_node::prepend_copy(const xml_node& proto) + { + xml_node_type type_ = proto.type(); + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + xml_node n(impl::allocate_node(alloc, type_)); + if (!n) return xml_node(); + + impl::prepend_node(n._root, _root); + impl::node_copy_tree(n._root, proto._root); + + return n; + } + + PUGI__FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) + { + xml_node_type type_ = proto.type(); + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + xml_node n(impl::allocate_node(alloc, type_)); + if (!n) return xml_node(); + + impl::insert_node_after(n._root, node._root); + impl::node_copy_tree(n._root, proto._root); + + return n; + } + + PUGI__FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) + { + xml_node_type type_ = proto.type(); + if (!impl::allow_insert_child(type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + xml_node n(impl::allocate_node(alloc, type_)); + if (!n) return xml_node(); + + impl::insert_node_before(n._root, node._root); + impl::node_copy_tree(n._root, proto._root); + + return n; + } + + PUGI__FN xml_node xml_node::append_move(const xml_node& moved) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers + impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; + + impl::remove_node(moved._root); + impl::append_node(moved._root, _root); + + return moved; + } + + PUGI__FN xml_node xml_node::prepend_move(const xml_node& moved) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers + impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; + + impl::remove_node(moved._root); + impl::prepend_node(moved._root, _root); + + return moved; + } + + PUGI__FN xml_node xml_node::insert_move_after(const xml_node& moved, const xml_node& node) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + if (moved._root == node._root) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers + impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; + + impl::remove_node(moved._root); + impl::insert_node_after(moved._root, node._root); + + return moved; + } + + PUGI__FN xml_node xml_node::insert_move_before(const xml_node& moved, const xml_node& node) + { + if (!impl::allow_move(*this, moved)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + if (moved._root == node._root) return xml_node(); + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return xml_node(); + + // disable document_buffer_order optimization since moving nodes around changes document order without changing buffer pointers + impl::get_document(_root).header |= impl::xml_memory_page_contents_shared_mask; + + impl::remove_node(moved._root); + impl::insert_node_before(moved._root, node._root); + + return moved; + } + + PUGI__FN bool xml_node::remove_attribute(const char_t* name_) + { + return remove_attribute(attribute(name_)); + } + + PUGI__FN bool xml_node::remove_attribute(const xml_attribute& a) + { + if (!_root || !a._attr) return false; + if (!impl::is_attribute_of(a._attr, _root)) return false; + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return false; + + impl::remove_attribute(a._attr, _root); + impl::destroy_attribute(a._attr, alloc); + + return true; + } + + PUGI__FN bool xml_node::remove_attributes() + { + if (!_root) return false; + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return false; + + for (xml_attribute_struct* attr = _root->first_attribute; attr; ) + { + xml_attribute_struct* next = attr->next_attribute; + + impl::destroy_attribute(attr, alloc); + + attr = next; + } + + _root->first_attribute = 0; + + return true; + } + + PUGI__FN bool xml_node::remove_child(const char_t* name_) + { + return remove_child(child(name_)); + } + + PUGI__FN bool xml_node::remove_child(const xml_node& n) + { + if (!_root || !n._root || n._root->parent != _root) return false; + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return false; + + impl::remove_node(n._root); + impl::destroy_node(n._root, alloc); + + return true; + } + + PUGI__FN bool xml_node::remove_children() + { + if (!_root) return false; + + impl::xml_allocator& alloc = impl::get_allocator(_root); + if (!alloc.reserve()) return false; + + for (xml_node_struct* cur = _root->first_child; cur; ) + { + xml_node_struct* next = cur->next_sibling; + + impl::destroy_node(cur, alloc); + + cur = next; + } + + _root->first_child = 0; + + return true; + } + + PUGI__FN xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + // append_buffer is only valid for elements/documents + if (!impl::allow_insert_child(type(), node_element)) return impl::make_parse_result(status_append_invalid_root); + + // get document node + impl::xml_document_struct* doc = &impl::get_document(_root); + + // disable document_buffer_order optimization since in a document with multiple buffers comparing buffer pointers does not make sense + doc->header |= impl::xml_memory_page_contents_shared_mask; + + // get extra buffer element (we'll store the document fragment buffer there so that we can deallocate it later) + impl::xml_memory_page* page = 0; + impl::xml_extra_buffer* extra = static_cast(doc->allocate_memory(sizeof(impl::xml_extra_buffer) + sizeof(void*), page)); + (void)page; + + if (!extra) return impl::make_parse_result(status_out_of_memory); + + #ifdef PUGIXML_COMPACT + // align the memory block to a pointer boundary; this is required for compact mode where memory allocations are only 4b aligned + // note that this requires up to sizeof(void*)-1 additional memory, which the allocation above takes into account + extra = reinterpret_cast((reinterpret_cast(extra) + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1)); + #endif + + // add extra buffer to the list + extra->buffer = 0; + extra->next = doc->extra_buffers; + doc->extra_buffers = extra; + + // name of the root has to be NULL before parsing - otherwise closing node mismatches will not be detected at the top level + impl::name_null_sentry sentry(_root); + + return impl::load_buffer_impl(doc, _root, const_cast(contents), size, options, encoding, false, false, &extra->buffer); + } + + PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) + { + for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) + if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value + 0 : PUGIXML_TEXT(""))) + return xml_node(i); + } + + return xml_node(); + } + + PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) + if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value + 0 : PUGIXML_TEXT(""))) + return xml_node(i); + + return xml_node(); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN string_t xml_node::path(char_t delimiter) const + { + if (!_root) return string_t(); + + size_t offset = 0; + + for (xml_node_struct* i = _root; i; i = i->parent) + { + offset += (i != _root); + offset += i->name ? impl::strlength(i->name) : 0; + } + + string_t result; + result.resize(offset); + + for (xml_node_struct* j = _root; j; j = j->parent) + { + if (j != _root) + result[--offset] = delimiter; + + if (j->name) + { + size_t length = impl::strlength(j->name); + + offset -= length; + memcpy(&result[offset], j->name, length * sizeof(char_t)); + } + } + + assert(offset == 0); + + return result; + } +#endif + + PUGI__FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const + { + xml_node context = path_[0] == delimiter ? root() : *this; + + if (!context._root) return xml_node(); + + const char_t* path_segment = path_; + + while (*path_segment == delimiter) ++path_segment; + + const char_t* path_segment_end = path_segment; + + while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end; + + if (path_segment == path_segment_end) return context; + + const char_t* next_segment = path_segment_end; + + while (*next_segment == delimiter) ++next_segment; + + if (*path_segment == '.' && path_segment + 1 == path_segment_end) + return context.first_element_by_path(next_segment, delimiter); + else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end) + return context.parent().first_element_by_path(next_segment, delimiter); + else + { + for (xml_node_struct* j = context._root->first_child; j; j = j->next_sibling) + { + if (j->name && impl::strequalrange(j->name, path_segment, static_cast(path_segment_end - path_segment))) + { + xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter); + + if (subsearch) return subsearch; + } + } + + return xml_node(); + } + } + + PUGI__FN bool xml_node::traverse(xml_tree_walker& walker) + { + walker._depth = -1; + + xml_node arg_begin(_root); + if (!walker.begin(arg_begin)) return false; + + xml_node_struct* cur = _root ? _root->first_child + 0 : 0; + + if (cur) + { + ++walker._depth; + + do + { + xml_node arg_for_each(cur); + if (!walker.for_each(arg_for_each)) + return false; + + if (cur->first_child) + { + ++walker._depth; + cur = cur->first_child; + } + else if (cur->next_sibling) + cur = cur->next_sibling; + else + { + while (!cur->next_sibling && cur != _root && cur->parent) + { + --walker._depth; + cur = cur->parent; + } + + if (cur != _root) + cur = cur->next_sibling; + } + } + while (cur && cur != _root); + } + + assert(walker._depth == -1); + + xml_node arg_end(_root); + return walker.end(arg_end); + } + + PUGI__FN size_t xml_node::hash_value() const + { + return static_cast(reinterpret_cast(_root) / sizeof(xml_node_struct)); + } + + PUGI__FN xml_node_struct* xml_node::internal_object() const + { + return _root; + } + + PUGI__FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const + { + if (!_root) return; + + impl::xml_buffered_writer buffered_writer(writer, encoding); + + impl::node_output(buffered_writer, _root, indent, flags, depth); + + buffered_writer.flush(); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const + { + xml_writer_stream writer(stream); + + print(writer, indent, flags, encoding, depth); + } + + PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, unsigned int depth) const + { + xml_writer_stream writer(stream); + + print(writer, indent, flags, encoding_wchar, depth); + } +#endif + + PUGI__FN ptrdiff_t xml_node::offset_debug() const + { + if (!_root) return -1; + + impl::xml_document_struct& doc = impl::get_document(_root); + + // we can determine the offset reliably only if there is exactly once parse buffer + if (!doc.buffer || doc.extra_buffers) return -1; + + switch (type()) + { + case node_document: + return 0; + + case node_element: + case node_declaration: + case node_pi: + return _root->name && (_root->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0 ? _root->name - doc.buffer : -1; + + case node_pcdata: + case node_cdata: + case node_comment: + case node_doctype: + return _root->value && (_root->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0 ? _root->value - doc.buffer : -1; + + default: + assert(false && "Invalid node type"); // unreachable + return -1; + } + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_node& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_node& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_text::xml_text(xml_node_struct* root): _root(root) + { + } + + PUGI__FN xml_node_struct* xml_text::_data() const + { + if (!_root || impl::is_text_node(_root)) return _root; + + // element nodes can have value if parse_embed_pcdata was used + if (PUGI__NODETYPE(_root) == node_element && _root->value) + return _root; + + for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling) + if (impl::is_text_node(node)) + return node; + + return 0; + } + + PUGI__FN xml_node_struct* xml_text::_data_new() + { + xml_node_struct* d = _data(); + if (d) return d; + + return xml_node(_root).append_child(node_pcdata).internal_object(); + } + + PUGI__FN xml_text::xml_text(): _root(0) + { + } + + PUGI__FN static void unspecified_bool_xml_text(xml_text***) + { + } + + PUGI__FN xml_text::operator xml_text::unspecified_bool_type() const + { + return _data() ? unspecified_bool_xml_text : 0; + } + + PUGI__FN bool xml_text::operator!() const + { + return !_data(); + } + + PUGI__FN bool xml_text::empty() const + { + return _data() == 0; + } + + PUGI__FN const char_t* xml_text::get() const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? d->value + 0 : PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_text::as_string(const char_t* def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? d->value + 0 : def; + } + + PUGI__FN int xml_text::as_int(int def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? impl::get_value_int(d->value) : def; + } + + PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? impl::get_value_uint(d->value) : def; + } + + PUGI__FN double xml_text::as_double(double def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? impl::get_value_double(d->value) : def; + } + + PUGI__FN float xml_text::as_float(float def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? impl::get_value_float(d->value) : def; + } + + PUGI__FN bool xml_text::as_bool(bool def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? impl::get_value_bool(d->value) : def; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long xml_text::as_llong(long long def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? impl::get_value_llong(d->value) : def; + } + + PUGI__FN unsigned long long xml_text::as_ullong(unsigned long long def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? impl::get_value_ullong(d->value) : def; + } +#endif + + PUGI__FN bool xml_text::set(const char_t* rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, impl::strlength(rhs)) : false; + } + + PUGI__FN bool xml_text::set(int rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false; + } + + PUGI__FN bool xml_text::set(unsigned int rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false; + } + + PUGI__FN bool xml_text::set(long rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false; + } + + PUGI__FN bool xml_text::set(unsigned long rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false; + } + + PUGI__FN bool xml_text::set(float rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, default_float_precision) : false; + } + + PUGI__FN bool xml_text::set(float rhs, int precision) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, precision) : false; + } + + PUGI__FN bool xml_text::set(double rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, default_double_precision) : false; + } + + PUGI__FN bool xml_text::set(double rhs, int precision) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, precision) : false; + } + + PUGI__FN bool xml_text::set(bool rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_bool(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool xml_text::set(long long rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, rhs < 0) : false; + } + + PUGI__FN bool xml_text::set(unsigned long long rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_integer(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs, false) : false; + } +#endif + + PUGI__FN xml_text& xml_text::operator=(const char_t* rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(int rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(unsigned int rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(long rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(unsigned long rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(double rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(float rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(bool rhs) + { + set(rhs); + return *this; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN xml_text& xml_text::operator=(long long rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(unsigned long long rhs) + { + set(rhs); + return *this; + } +#endif + + PUGI__FN xml_node xml_text::data() const + { + return xml_node(_data()); + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_text& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_text& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_node_iterator::xml_node_iterator() + { + } + + PUGI__FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent()) + { + } + + PUGI__FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) + { + } + + PUGI__FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const + { + return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const + { + return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_node& xml_node_iterator::operator*() const + { + assert(_wrap._root); + return _wrap; + } + + PUGI__FN xml_node* xml_node_iterator::operator->() const + { + assert(_wrap._root); + return const_cast(&_wrap); // BCC5 workaround + } + + PUGI__FN xml_node_iterator& xml_node_iterator::operator++() + { + assert(_wrap._root); + _wrap._root = _wrap._root->next_sibling; + return *this; + } + + PUGI__FN xml_node_iterator xml_node_iterator::operator++(int) + { + xml_node_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN xml_node_iterator& xml_node_iterator::operator--() + { + _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child(); + return *this; + } + + PUGI__FN xml_node_iterator xml_node_iterator::operator--(int) + { + xml_node_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator() + { + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent) + { + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) + { + } + + PUGI__FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const + { + return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const + { + return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_attribute& xml_attribute_iterator::operator*() const + { + assert(_wrap._attr); + return _wrap; + } + + PUGI__FN xml_attribute* xml_attribute_iterator::operator->() const + { + assert(_wrap._attr); + return const_cast(&_wrap); // BCC5 workaround + } + + PUGI__FN xml_attribute_iterator& xml_attribute_iterator::operator++() + { + assert(_wrap._attr); + _wrap._attr = _wrap._attr->next_attribute; + return *this; + } + + PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator++(int) + { + xml_attribute_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN xml_attribute_iterator& xml_attribute_iterator::operator--() + { + _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute(); + return *this; + } + + PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator--(int) + { + xml_attribute_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(): _name(0) + { + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _wrap(node), _parent(node.parent()), _name(name) + { + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name): _wrap(ref), _parent(parent), _name(name) + { + } + + PUGI__FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const + { + return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const + { + return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_node& xml_named_node_iterator::operator*() const + { + assert(_wrap._root); + return _wrap; + } + + PUGI__FN xml_node* xml_named_node_iterator::operator->() const + { + assert(_wrap._root); + return const_cast(&_wrap); // BCC5 workaround + } + + PUGI__FN xml_named_node_iterator& xml_named_node_iterator::operator++() + { + assert(_wrap._root); + _wrap = _wrap.next_sibling(_name); + return *this; + } + + PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator++(int) + { + xml_named_node_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN xml_named_node_iterator& xml_named_node_iterator::operator--() + { + if (_wrap._root) + _wrap = _wrap.previous_sibling(_name); + else + { + _wrap = _parent.last_child(); + + if (!impl::strequal(_wrap.name(), _name)) + _wrap = _wrap.previous_sibling(_name); + } + + return *this; + } + + PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator--(int) + { + xml_named_node_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto) + { + } + + PUGI__FN xml_parse_result::operator bool() const + { + return status == status_ok; + } + + PUGI__FN const char* xml_parse_result::description() const + { + switch (status) + { + case status_ok: return "No error"; + + case status_file_not_found: return "File was not found"; + case status_io_error: return "Error reading from file/stream"; + case status_out_of_memory: return "Could not allocate memory"; + case status_internal_error: return "Internal error occurred"; + + case status_unrecognized_tag: return "Could not determine tag type"; + + case status_bad_pi: return "Error parsing document declaration/processing instruction"; + case status_bad_comment: return "Error parsing comment"; + case status_bad_cdata: return "Error parsing CDATA section"; + case status_bad_doctype: return "Error parsing document type declaration"; + case status_bad_pcdata: return "Error parsing PCDATA section"; + case status_bad_start_element: return "Error parsing start element tag"; + case status_bad_attribute: return "Error parsing element attribute"; + case status_bad_end_element: return "Error parsing end element tag"; + case status_end_element_mismatch: return "Start-end tags mismatch"; + + case status_append_invalid_root: return "Unable to append nodes: root is not an element or document"; + + case status_no_document_element: return "No document element found"; + + default: return "Unknown error"; + } + } + + PUGI__FN xml_document::xml_document(): _buffer(0) + { + _create(); + } + + PUGI__FN xml_document::~xml_document() + { + _destroy(); + } + +#ifdef PUGIXML_HAS_MOVE + PUGI__FN xml_document::xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT: _buffer(0) + { + _create(); + _move(rhs); + } + + PUGI__FN xml_document& xml_document::operator=(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT + { + if (this == &rhs) return *this; + + _destroy(); + _create(); + _move(rhs); + + return *this; + } +#endif + + PUGI__FN void xml_document::reset() + { + _destroy(); + _create(); + } + + PUGI__FN void xml_document::reset(const xml_document& proto) + { + reset(); + + impl::node_copy_tree(_root, proto._root); + } + + PUGI__FN void xml_document::_create() + { + assert(!_root); + + #ifdef PUGIXML_COMPACT + // space for page marker for the first page (uint32_t), rounded up to pointer size; assumes pointers are at least 32-bit + const size_t page_offset = sizeof(void*); + #else + const size_t page_offset = 0; + #endif + + // initialize sentinel page + PUGI__STATIC_ASSERT(sizeof(impl::xml_memory_page) + sizeof(impl::xml_document_struct) + page_offset <= sizeof(_memory)); + + // prepare page structure + impl::xml_memory_page* page = impl::xml_memory_page::construct(_memory); + assert(page); + + page->busy_size = impl::xml_memory_page_size; + + // setup first page marker + #ifdef PUGIXML_COMPACT + // round-trip through void* to avoid 'cast increases required alignment of target type' warning + page->compact_page_marker = reinterpret_cast(static_cast(reinterpret_cast(page) + sizeof(impl::xml_memory_page))); + *page->compact_page_marker = sizeof(impl::xml_memory_page); + #endif + + // allocate new root + _root = new (reinterpret_cast(page) + sizeof(impl::xml_memory_page) + page_offset) impl::xml_document_struct(page); + _root->prev_sibling_c = _root; + + // setup sentinel page + page->allocator = static_cast(_root); + + // setup hash table pointer in allocator + #ifdef PUGIXML_COMPACT + page->allocator->_hash = &static_cast(_root)->hash; + #endif + + // verify the document allocation + assert(reinterpret_cast(_root) + sizeof(impl::xml_document_struct) <= _memory + sizeof(_memory)); + } + + PUGI__FN void xml_document::_destroy() + { + assert(_root); + + // destroy static storage + if (_buffer) + { + impl::xml_memory::deallocate(_buffer); + _buffer = 0; + } + + // destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator) + for (impl::xml_extra_buffer* extra = static_cast(_root)->extra_buffers; extra; extra = extra->next) + { + if (extra->buffer) impl::xml_memory::deallocate(extra->buffer); + } + + // destroy dynamic storage, leave sentinel page (it's in static memory) + impl::xml_memory_page* root_page = PUGI__GETPAGE(_root); + assert(root_page && !root_page->prev); + assert(reinterpret_cast(root_page) >= _memory && reinterpret_cast(root_page) < _memory + sizeof(_memory)); + + for (impl::xml_memory_page* page = root_page->next; page; ) + { + impl::xml_memory_page* next = page->next; + + impl::xml_allocator::deallocate_page(page); + + page = next; + } + + #ifdef PUGIXML_COMPACT + // destroy hash table + static_cast(_root)->hash.clear(); + #endif + + _root = 0; + } + +#ifdef PUGIXML_HAS_MOVE + PUGI__FN void xml_document::_move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT + { + impl::xml_document_struct* doc = static_cast(_root); + impl::xml_document_struct* other = static_cast(rhs._root); + + // save first child pointer for later; this needs hash access + xml_node_struct* other_first_child = other->first_child; + + #ifdef PUGIXML_COMPACT + // reserve space for the hash table up front; this is the only operation that can fail + // if it does, we have no choice but to throw (if we have exceptions) + if (other_first_child) + { + size_t other_children = 0; + for (xml_node_struct* node = other_first_child; node; node = node->next_sibling) + other_children++; + + // in compact mode, each pointer assignment could result in a hash table request + // during move, we have to relocate document first_child and parents of all children + // normally there's just one child and its parent has a pointerless encoding but + // we assume the worst here + if (!other->_hash->reserve(other_children + 1)) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return; + #else + throw std::bad_alloc(); + #endif + } + } + #endif + + // move allocation state + // note that other->_root may point to the embedded document page, in which case we should keep original (empty) state + if (other->_root != PUGI__GETPAGE(other)) + { + doc->_root = other->_root; + doc->_busy_size = other->_busy_size; + } + + // move buffer state + doc->buffer = other->buffer; + doc->extra_buffers = other->extra_buffers; + _buffer = rhs._buffer; + + #ifdef PUGIXML_COMPACT + // move compact hash; note that the hash table can have pointers to other but they will be "inactive", similarly to nodes removed with remove_child + doc->hash = other->hash; + doc->_hash = &doc->hash; + + // make sure we don't access other hash up until the end when we reinitialize other document + other->_hash = 0; + #endif + + // move page structure + impl::xml_memory_page* doc_page = PUGI__GETPAGE(doc); + assert(doc_page && !doc_page->prev && !doc_page->next); + + impl::xml_memory_page* other_page = PUGI__GETPAGE(other); + assert(other_page && !other_page->prev); + + // relink pages since root page is embedded into xml_document + if (impl::xml_memory_page* page = other_page->next) + { + assert(page->prev == other_page); + + page->prev = doc_page; + + doc_page->next = page; + other_page->next = 0; + } + + // make sure pages point to the correct document state + for (impl::xml_memory_page* page = doc_page->next; page; page = page->next) + { + assert(page->allocator == other); + + page->allocator = doc; + + #ifdef PUGIXML_COMPACT + // this automatically migrates most children between documents and prevents ->parent assignment from allocating + if (page->compact_shared_parent == other) + page->compact_shared_parent = doc; + #endif + } + + // move tree structure + assert(!doc->first_child); + + doc->first_child = other_first_child; + + for (xml_node_struct* node = other_first_child; node; node = node->next_sibling) + { + #ifdef PUGIXML_COMPACT + // most children will have migrated when we reassigned compact_shared_parent + assert(node->parent == other || node->parent == doc); + + node->parent = doc; + #else + assert(node->parent == other); + node->parent = doc; + #endif + } + + // reset other document + new (other) impl::xml_document_struct(PUGI__GETPAGE(other)); + rhs._buffer = 0; + } +#endif + +#ifndef PUGIXML_NO_STL + PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_stream_impl(static_cast(_root), stream, options, encoding, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options) + { + reset(); + + return impl::load_stream_impl(static_cast(_root), stream, options, encoding_wchar, &_buffer); + } +#endif + + PUGI__FN xml_parse_result xml_document::load_string(const char_t* contents, unsigned int options) + { + // Force native encoding (skip autodetection) + #ifdef PUGIXML_WCHAR_MODE + xml_encoding encoding = encoding_wchar; + #else + xml_encoding encoding = encoding_utf8; + #endif + + return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) + { + return load_string(contents, options); + } + + PUGI__FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) + { + reset(); + + using impl::auto_deleter; // MSVC7 workaround + auto_deleter file(impl::open_file(path_, "rb"), impl::close_file); + + return impl::load_file_impl(static_cast(_root), file.data, options, encoding, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) + { + reset(); + + using impl::auto_deleter; // MSVC7 workaround + auto_deleter file(impl::open_file_wide(path_, L"rb"), impl::close_file); + + return impl::load_file_impl(static_cast(_root), file.data, options, encoding, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast(_root), _root, const_cast(contents), size, options, encoding, false, false, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, false, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, true, &_buffer); + } + + PUGI__FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + impl::xml_buffered_writer buffered_writer(writer, encoding); + + if ((flags & format_write_bom) && encoding != encoding_latin1) + { + // BOM always represents the codepoint U+FEFF, so just write it in native encoding + #ifdef PUGIXML_WCHAR_MODE + unsigned int bom = 0xfeff; + buffered_writer.write(static_cast(bom)); + #else + buffered_writer.write('\xef', '\xbb', '\xbf'); + #endif + } + + if (!(flags & format_no_declaration) && !impl::has_declaration(_root)) + { + buffered_writer.write_string(PUGIXML_TEXT("'); + if (!(flags & format_raw)) buffered_writer.write('\n'); + } + + impl::node_output(buffered_writer, _root, indent, flags, 0); + + buffered_writer.flush(); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + xml_writer_stream writer(stream); + + save(writer, indent, flags, encoding); + } + + PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags) const + { + xml_writer_stream writer(stream); + + save(writer, indent, flags, encoding_wchar); + } +#endif + + PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + using impl::auto_deleter; // MSVC7 workaround + auto_deleter file(impl::open_file(path_, (flags & format_save_file_text) ? "w" : "wb"), impl::close_file); + + return impl::save_file_impl(*this, file.data, indent, flags, encoding); + } + + PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + using impl::auto_deleter; // MSVC7 workaround + auto_deleter file(impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"), impl::close_file); + + return impl::save_file_impl(*this, file.data, indent, flags, encoding); + } + + PUGI__FN xml_node xml_document::document_element() const + { + assert(_root); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (PUGI__NODETYPE(i) == node_element) + return xml_node(i); + + return xml_node(); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) + { + assert(str); + + return impl::as_utf8_impl(str, impl::strlength_wide(str)); + } + + PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string& str) + { + return impl::as_utf8_impl(str.c_str(), str.size()); + } + + PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const char* str) + { + assert(str); + + return impl::as_wide_impl(str, strlen(str)); + } + + PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const std::string& str) + { + return impl::as_wide_impl(str.c_str(), str.size()); + } +#endif + + PUGI__FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) + { + impl::xml_memory::allocate = allocate; + impl::xml_memory::deallocate = deallocate; + } + + PUGI__FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() + { + return impl::xml_memory::allocate; + } + + PUGI__FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() + { + return impl::xml_memory::deallocate; + } +} + +#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) +namespace std +{ + // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } +} +#endif + +#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) +namespace std +{ + // Workarounds for (non-standard) iterator category detection + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } +} +#endif + +#ifndef PUGIXML_NO_XPATH +// STL replacements +PUGI__NS_BEGIN + struct equal_to + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs == rhs; + } + }; + + struct not_equal_to + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs != rhs; + } + }; + + struct less + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs < rhs; + } + }; + + struct less_equal + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs <= rhs; + } + }; + + template inline void swap(T& lhs, T& rhs) + { + T temp = lhs; + lhs = rhs; + rhs = temp; + } + + template PUGI__FN I min_element(I begin, I end, const Pred& pred) + { + I result = begin; + + for (I it = begin + 1; it != end; ++it) + if (pred(*it, *result)) + result = it; + + return result; + } + + template PUGI__FN void reverse(I begin, I end) + { + while (end - begin > 1) + swap(*begin++, *--end); + } + + template PUGI__FN I unique(I begin, I end) + { + // fast skip head + while (end - begin > 1 && *begin != *(begin + 1)) + begin++; + + if (begin == end) + return begin; + + // last written element + I write = begin++; + + // merge unique elements + while (begin != end) + { + if (*begin != *write) + *++write = *begin++; + else + begin++; + } + + // past-the-end (write points to live element) + return write + 1; + } + + template PUGI__FN void insertion_sort(T* begin, T* end, const Pred& pred) + { + if (begin == end) + return; + + for (T* it = begin + 1; it != end; ++it) + { + T val = *it; + T* hole = it; + + // move hole backwards + while (hole > begin && pred(val, *(hole - 1))) + { + *hole = *(hole - 1); + hole--; + } + + // fill hole with element + *hole = val; + } + } + + template inline I median3(I first, I middle, I last, const Pred& pred) + { + if (pred(*middle, *first)) + swap(middle, first); + if (pred(*last, *middle)) + swap(last, middle); + if (pred(*middle, *first)) + swap(middle, first); + + return middle; + } + + template PUGI__FN void partition3(T* begin, T* end, T pivot, const Pred& pred, T** out_eqbeg, T** out_eqend) + { + // invariant: array is split into 4 groups: = < ? > (each variable denotes the boundary between the groups) + T* eq = begin; + T* lt = begin; + T* gt = end; + + while (lt < gt) + { + if (pred(*lt, pivot)) + lt++; + else if (*lt == pivot) + swap(*eq++, *lt++); + else + swap(*lt, *--gt); + } + + // we now have just 4 groups: = < >; move equal elements to the middle + T* eqbeg = gt; + + for (T* it = begin; it != eq; ++it) + swap(*it, *--eqbeg); + + *out_eqbeg = eqbeg; + *out_eqend = gt; + } + + template PUGI__FN void sort(I begin, I end, const Pred& pred) + { + // sort large chunks + while (end - begin > 16) + { + // find median element + I middle = begin + (end - begin) / 2; + I median = median3(begin, middle, end - 1, pred); + + // partition in three chunks (< = >) + I eqbeg, eqend; + partition3(begin, end, *median, pred, &eqbeg, &eqend); + + // loop on larger half + if (eqbeg - begin > end - eqend) + { + sort(eqend, end, pred); + end = eqbeg; + } + else + { + sort(begin, eqbeg, pred); + begin = eqend; + } + } + + // insertion sort small chunk + insertion_sort(begin, end, pred); + } + + PUGI__FN bool hash_insert(const void** table, size_t size, const void* key) + { + assert(key); + + unsigned int h = static_cast(reinterpret_cast(key)); + + // MurmurHash3 32-bit finalizer + h ^= h >> 16; + h *= 0x85ebca6bu; + h ^= h >> 13; + h *= 0xc2b2ae35u; + h ^= h >> 16; + + size_t hashmod = size - 1; + size_t bucket = h & hashmod; + + for (size_t probe = 0; probe <= hashmod; ++probe) + { + if (table[bucket] == 0) + { + table[bucket] = key; + return true; + } + + if (table[bucket] == key) + return false; + + // hash collision, quadratic probing + bucket = (bucket + probe + 1) & hashmod; + } + + assert(false && "Hash table is full"); // unreachable + return false; + } +PUGI__NS_END + +// Allocator used for AST and evaluation stacks +PUGI__NS_BEGIN + static const size_t xpath_memory_page_size = + #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE + PUGIXML_MEMORY_XPATH_PAGE_SIZE + #else + 4096 + #endif + ; + + static const uintptr_t xpath_memory_block_alignment = sizeof(double) > sizeof(void*) ? sizeof(double) : sizeof(void*); + + struct xpath_memory_block + { + xpath_memory_block* next; + size_t capacity; + + union + { + char data[xpath_memory_page_size]; + double alignment; + }; + }; + + struct xpath_allocator + { + xpath_memory_block* _root; + size_t _root_size; + bool* _error; + + xpath_allocator(xpath_memory_block* root, bool* error = 0): _root(root), _root_size(0), _error(error) + { + } + + void* allocate(size_t size) + { + // round size up to block alignment boundary + size = (size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1); + + if (_root_size + size <= _root->capacity) + { + void* buf = &_root->data[0] + _root_size; + _root_size += size; + return buf; + } + else + { + // make sure we have at least 1/4th of the page free after allocation to satisfy subsequent allocation requests + size_t block_capacity_base = sizeof(_root->data); + size_t block_capacity_req = size + block_capacity_base / 4; + size_t block_capacity = (block_capacity_base > block_capacity_req) ? block_capacity_base : block_capacity_req; + + size_t block_size = block_capacity + offsetof(xpath_memory_block, data); + + xpath_memory_block* block = static_cast(xml_memory::allocate(block_size)); + if (!block) + { + if (_error) *_error = true; + return 0; + } + + block->next = _root; + block->capacity = block_capacity; + + _root = block; + _root_size = size; + + return block->data; + } + } + + void* reallocate(void* ptr, size_t old_size, size_t new_size) + { + // round size up to block alignment boundary + old_size = (old_size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1); + new_size = (new_size + xpath_memory_block_alignment - 1) & ~(xpath_memory_block_alignment - 1); + + // we can only reallocate the last object + assert(ptr == 0 || static_cast(ptr) + old_size == &_root->data[0] + _root_size); + + // try to reallocate the object inplace + if (ptr && _root_size - old_size + new_size <= _root->capacity) + { + _root_size = _root_size - old_size + new_size; + return ptr; + } + + // allocate a new block + void* result = allocate(new_size); + if (!result) return 0; + + // we have a new block + if (ptr) + { + // copy old data (we only support growing) + assert(new_size >= old_size); + memcpy(result, ptr, old_size); + + // free the previous page if it had no other objects + assert(_root->data == result); + assert(_root->next); + + if (_root->next->data == ptr) + { + // deallocate the whole page, unless it was the first one + xpath_memory_block* next = _root->next->next; + + if (next) + { + xml_memory::deallocate(_root->next); + _root->next = next; + } + } + } + + return result; + } + + void revert(const xpath_allocator& state) + { + // free all new pages + xpath_memory_block* cur = _root; + + while (cur != state._root) + { + xpath_memory_block* next = cur->next; + + xml_memory::deallocate(cur); + + cur = next; + } + + // restore state + _root = state._root; + _root_size = state._root_size; + } + + void release() + { + xpath_memory_block* cur = _root; + assert(cur); + + while (cur->next) + { + xpath_memory_block* next = cur->next; + + xml_memory::deallocate(cur); + + cur = next; + } + } + }; + + struct xpath_allocator_capture + { + xpath_allocator_capture(xpath_allocator* alloc): _target(alloc), _state(*alloc) + { + } + + ~xpath_allocator_capture() + { + _target->revert(_state); + } + + xpath_allocator* _target; + xpath_allocator _state; + }; + + struct xpath_stack + { + xpath_allocator* result; + xpath_allocator* temp; + }; + + struct xpath_stack_data + { + xpath_memory_block blocks[2]; + xpath_allocator result; + xpath_allocator temp; + xpath_stack stack; + bool oom; + + xpath_stack_data(): result(blocks + 0, &oom), temp(blocks + 1, &oom), oom(false) + { + blocks[0].next = blocks[1].next = 0; + blocks[0].capacity = blocks[1].capacity = sizeof(blocks[0].data); + + stack.result = &result; + stack.temp = &temp; + } + + ~xpath_stack_data() + { + result.release(); + temp.release(); + } + }; +PUGI__NS_END + +// String class +PUGI__NS_BEGIN + class xpath_string + { + const char_t* _buffer; + bool _uses_heap; + size_t _length_heap; + + static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc) + { + char_t* result = static_cast(alloc->allocate((length + 1) * sizeof(char_t))); + if (!result) return 0; + + memcpy(result, string, length * sizeof(char_t)); + result[length] = 0; + + return result; + } + + xpath_string(const char_t* buffer, bool uses_heap_, size_t length_heap): _buffer(buffer), _uses_heap(uses_heap_), _length_heap(length_heap) + { + } + + public: + static xpath_string from_const(const char_t* str) + { + return xpath_string(str, false, 0); + } + + static xpath_string from_heap_preallocated(const char_t* begin, const char_t* end) + { + assert(begin <= end && *end == 0); + + return xpath_string(begin, true, static_cast(end - begin)); + } + + static xpath_string from_heap(const char_t* begin, const char_t* end, xpath_allocator* alloc) + { + assert(begin <= end); + + if (begin == end) + return xpath_string(); + + size_t length = static_cast(end - begin); + const char_t* data = duplicate_string(begin, length, alloc); + + return data ? xpath_string(data, true, length) : xpath_string(); + } + + xpath_string(): _buffer(PUGIXML_TEXT("")), _uses_heap(false), _length_heap(0) + { + } + + void append(const xpath_string& o, xpath_allocator* alloc) + { + // skip empty sources + if (!*o._buffer) return; + + // fast append for constant empty target and constant source + if (!*_buffer && !_uses_heap && !o._uses_heap) + { + _buffer = o._buffer; + } + else + { + // need to make heap copy + size_t target_length = length(); + size_t source_length = o.length(); + size_t result_length = target_length + source_length; + + // allocate new buffer + char_t* result = static_cast(alloc->reallocate(_uses_heap ? const_cast(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); + if (!result) return; + + // append first string to the new buffer in case there was no reallocation + if (!_uses_heap) memcpy(result, _buffer, target_length * sizeof(char_t)); + + // append second string to the new buffer + memcpy(result + target_length, o._buffer, source_length * sizeof(char_t)); + result[result_length] = 0; + + // finalize + _buffer = result; + _uses_heap = true; + _length_heap = result_length; + } + } + + const char_t* c_str() const + { + return _buffer; + } + + size_t length() const + { + return _uses_heap ? _length_heap : strlength(_buffer); + } + + char_t* data(xpath_allocator* alloc) + { + // make private heap copy + if (!_uses_heap) + { + size_t length_ = strlength(_buffer); + const char_t* data_ = duplicate_string(_buffer, length_, alloc); + + if (!data_) return 0; + + _buffer = data_; + _uses_heap = true; + _length_heap = length_; + } + + return const_cast(_buffer); + } + + bool empty() const + { + return *_buffer == 0; + } + + bool operator==(const xpath_string& o) const + { + return strequal(_buffer, o._buffer); + } + + bool operator!=(const xpath_string& o) const + { + return !strequal(_buffer, o._buffer); + } + + bool uses_heap() const + { + return _uses_heap; + } + }; +PUGI__NS_END + +PUGI__NS_BEGIN + PUGI__FN bool starts_with(const char_t* string, const char_t* pattern) + { + while (*pattern && *string == *pattern) + { + string++; + pattern++; + } + + return *pattern == 0; + } + + PUGI__FN const char_t* find_char(const char_t* s, char_t c) + { + #ifdef PUGIXML_WCHAR_MODE + return wcschr(s, c); + #else + return strchr(s, c); + #endif + } + + PUGI__FN const char_t* find_substring(const char_t* s, const char_t* p) + { + #ifdef PUGIXML_WCHAR_MODE + // MSVC6 wcsstr bug workaround (if s is empty it always returns 0) + return (*p == 0) ? s : wcsstr(s, p); + #else + return strstr(s, p); + #endif + } + + // Converts symbol to lower case, if it is an ASCII one + PUGI__FN char_t tolower_ascii(char_t ch) + { + return static_cast(ch - 'A') < 26 ? static_cast(ch | ' ') : ch; + } + + PUGI__FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) + { + if (na.attribute()) + return xpath_string::from_const(na.attribute().value()); + else + { + xml_node n = na.node(); + + switch (n.type()) + { + case node_pcdata: + case node_cdata: + case node_comment: + case node_pi: + return xpath_string::from_const(n.value()); + + case node_document: + case node_element: + { + xpath_string result; + + // element nodes can have value if parse_embed_pcdata was used + if (n.value()[0]) + result.append(xpath_string::from_const(n.value()), alloc); + + xml_node cur = n.first_child(); + + while (cur && cur != n) + { + if (cur.type() == node_pcdata || cur.type() == node_cdata) + result.append(xpath_string::from_const(cur.value()), alloc); + + if (cur.first_child()) + cur = cur.first_child(); + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + while (!cur.next_sibling() && cur != n) + cur = cur.parent(); + + if (cur != n) cur = cur.next_sibling(); + } + } + + return result; + } + + default: + return xpath_string(); + } + } + } + + PUGI__FN bool node_is_before_sibling(xml_node_struct* ln, xml_node_struct* rn) + { + assert(ln->parent == rn->parent); + + // there is no common ancestor (the shared parent is null), nodes are from different documents + if (!ln->parent) return ln < rn; + + // determine sibling order + xml_node_struct* ls = ln; + xml_node_struct* rs = rn; + + while (ls && rs) + { + if (ls == rn) return true; + if (rs == ln) return false; + + ls = ls->next_sibling; + rs = rs->next_sibling; + } + + // if rn sibling chain ended ln must be before rn + return !rs; + } + + PUGI__FN bool node_is_before(xml_node_struct* ln, xml_node_struct* rn) + { + // find common ancestor at the same depth, if any + xml_node_struct* lp = ln; + xml_node_struct* rp = rn; + + while (lp && rp && lp->parent != rp->parent) + { + lp = lp->parent; + rp = rp->parent; + } + + // parents are the same! + if (lp && rp) return node_is_before_sibling(lp, rp); + + // nodes are at different depths, need to normalize heights + bool left_higher = !lp; + + while (lp) + { + lp = lp->parent; + ln = ln->parent; + } + + while (rp) + { + rp = rp->parent; + rn = rn->parent; + } + + // one node is the ancestor of the other + if (ln == rn) return left_higher; + + // find common ancestor... again + while (ln->parent != rn->parent) + { + ln = ln->parent; + rn = rn->parent; + } + + return node_is_before_sibling(ln, rn); + } + + PUGI__FN bool node_is_ancestor(xml_node_struct* parent, xml_node_struct* node) + { + while (node && node != parent) node = node->parent; + + return parent && node == parent; + } + + PUGI__FN const void* document_buffer_order(const xpath_node& xnode) + { + xml_node_struct* node = xnode.node().internal_object(); + + if (node) + { + if ((get_document(node).header & xml_memory_page_contents_shared_mask) == 0) + { + if (node->name && (node->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0) return node->name; + if (node->value && (node->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return node->value; + } + + return 0; + } + + xml_attribute_struct* attr = xnode.attribute().internal_object(); + + if (attr) + { + if ((get_document(attr).header & xml_memory_page_contents_shared_mask) == 0) + { + if ((attr->header & impl::xml_memory_page_name_allocated_or_shared_mask) == 0) return attr->name; + if ((attr->header & impl::xml_memory_page_value_allocated_or_shared_mask) == 0) return attr->value; + } + + return 0; + } + + return 0; + } + + struct document_order_comparator + { + bool operator()(const xpath_node& lhs, const xpath_node& rhs) const + { + // optimized document order based check + const void* lo = document_buffer_order(lhs); + const void* ro = document_buffer_order(rhs); + + if (lo && ro) return lo < ro; + + // slow comparison + xml_node ln = lhs.node(), rn = rhs.node(); + + // compare attributes + if (lhs.attribute() && rhs.attribute()) + { + // shared parent + if (lhs.parent() == rhs.parent()) + { + // determine sibling order + for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute()) + if (a == rhs.attribute()) + return true; + + return false; + } + + // compare attribute parents + ln = lhs.parent(); + rn = rhs.parent(); + } + else if (lhs.attribute()) + { + // attributes go after the parent element + if (lhs.parent() == rhs.node()) return false; + + ln = lhs.parent(); + } + else if (rhs.attribute()) + { + // attributes go after the parent element + if (rhs.parent() == lhs.node()) return true; + + rn = rhs.parent(); + } + + if (ln == rn) return false; + + if (!ln || !rn) return ln < rn; + + return node_is_before(ln.internal_object(), rn.internal_object()); + } + }; + + PUGI__FN double gen_nan() + { + #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24)) + PUGI__STATIC_ASSERT(sizeof(float) == sizeof(uint32_t)); + typedef uint32_t UI; // BCC5 workaround + union { float f; UI i; } u; + u.i = 0x7fc00000; + return double(u.f); + #else + // fallback + const volatile double zero = 0.0; + return zero / zero; + #endif + } + + PUGI__FN bool is_nan(double value) + { + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + return !!_isnan(value); + #elif defined(fpclassify) && defined(FP_NAN) + return fpclassify(value) == FP_NAN; + #else + // fallback + const volatile double v = value; + return v != v; + #endif + } + + PUGI__FN const char_t* convert_number_to_string_special(double value) + { + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0; + if (_isnan(value)) return PUGIXML_TEXT("NaN"); + return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO) + switch (fpclassify(value)) + { + case FP_NAN: + return PUGIXML_TEXT("NaN"); + + case FP_INFINITE: + return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + + case FP_ZERO: + return PUGIXML_TEXT("0"); + + default: + return 0; + } + #else + // fallback + const volatile double v = value; + + if (v == 0) return PUGIXML_TEXT("0"); + if (v != v) return PUGIXML_TEXT("NaN"); + if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + return 0; + #endif + } + + PUGI__FN bool convert_number_to_boolean(double value) + { + return (value != 0 && !is_nan(value)); + } + + PUGI__FN void truncate_zeros(char* begin, char* end) + { + while (begin != end && end[-1] == '0') end--; + + *end = 0; + } + + // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent +#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 + PUGI__FN void convert_number_to_mantissa_exponent(double value, char (&buffer)[32], char** out_mantissa, int* out_exponent) + { + // get base values + int sign, exponent; + _ecvt_s(buffer, sizeof(buffer), value, DBL_DIG + 1, &exponent, &sign); + + // truncate redundant zeros + truncate_zeros(buffer, buffer + strlen(buffer)); + + // fill results + *out_mantissa = buffer; + *out_exponent = exponent; + } +#else + PUGI__FN void convert_number_to_mantissa_exponent(double value, char (&buffer)[32], char** out_mantissa, int* out_exponent) + { + // get a scientific notation value with IEEE DBL_DIG decimals + PUGI__SNPRINTF(buffer, "%.*e", DBL_DIG, value); + + // get the exponent (possibly negative) + char* exponent_string = strchr(buffer, 'e'); + assert(exponent_string); + + int exponent = atoi(exponent_string + 1); + + // extract mantissa string: skip sign + char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer; + assert(mantissa[0] != '0' && mantissa[1] == '.'); + + // divide mantissa by 10 to eliminate integer part + mantissa[1] = mantissa[0]; + mantissa++; + exponent++; + + // remove extra mantissa digits and zero-terminate mantissa + truncate_zeros(mantissa, exponent_string); + + // fill results + *out_mantissa = mantissa; + *out_exponent = exponent; + } +#endif + + PUGI__FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) + { + // try special number conversion + const char_t* special = convert_number_to_string_special(value); + if (special) return xpath_string::from_const(special); + + // get mantissa + exponent form + char mantissa_buffer[32]; + + char* mantissa; + int exponent; + convert_number_to_mantissa_exponent(value, mantissa_buffer, &mantissa, &exponent); + + // allocate a buffer of suitable length for the number + size_t result_size = strlen(mantissa_buffer) + (exponent > 0 ? exponent : -exponent) + 4; + char_t* result = static_cast(alloc->allocate(sizeof(char_t) * result_size)); + if (!result) return xpath_string(); + + // make the number! + char_t* s = result; + + // sign + if (value < 0) *s++ = '-'; + + // integer part + if (exponent <= 0) + { + *s++ = '0'; + } + else + { + while (exponent > 0) + { + assert(*mantissa == 0 || static_cast(*mantissa - '0') <= 9); + *s++ = *mantissa ? *mantissa++ : '0'; + exponent--; + } + } + + // fractional part + if (*mantissa) + { + // decimal point + *s++ = '.'; + + // extra zeroes from negative exponent + while (exponent < 0) + { + *s++ = '0'; + exponent++; + } + + // extra mantissa digits + while (*mantissa) + { + assert(static_cast(*mantissa - '0') <= 9); + *s++ = *mantissa++; + } + } + + // zero-terminate + assert(s < result + result_size); + *s = 0; + + return xpath_string::from_heap_preallocated(result, s); + } + + PUGI__FN bool check_string_to_number_format(const char_t* string) + { + // parse leading whitespace + while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + + // parse sign + if (*string == '-') ++string; + + if (!*string) return false; + + // if there is no integer part, there should be a decimal part with at least one digit + if (!PUGI__IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI__IS_CHARTYPEX(string[1], ctx_digit))) return false; + + // parse integer part + while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + + // parse decimal part + if (*string == '.') + { + ++string; + + while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + } + + // parse trailing whitespace + while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + + return *string == 0; + } + + PUGI__FN double convert_string_to_number(const char_t* string) + { + // check string format + if (!check_string_to_number_format(string)) return gen_nan(); + + // parse string + #ifdef PUGIXML_WCHAR_MODE + return wcstod(string, 0); + #else + return strtod(string, 0); + #endif + } + + PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result) + { + size_t length = static_cast(end - begin); + char_t* scratch = buffer; + + if (length >= sizeof(buffer) / sizeof(buffer[0])) + { + // need to make dummy on-heap copy + scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!scratch) return false; + } + + // copy string to zero-terminated buffer and perform conversion + memcpy(scratch, begin, length * sizeof(char_t)); + scratch[length] = 0; + + *out_result = convert_string_to_number(scratch); + + // free dummy buffer + if (scratch != buffer) xml_memory::deallocate(scratch); + + return true; + } + + PUGI__FN double round_nearest(double value) + { + return floor(value + 0.5); + } + + PUGI__FN double round_nearest_nzero(double value) + { + // same as round_nearest, but returns -0 for [-0.5, -0] + // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0) + return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5); + } + + PUGI__FN const char_t* qualified_name(const xpath_node& node) + { + return node.attribute() ? node.attribute().name() : node.node().name(); + } + + PUGI__FN const char_t* local_name(const xpath_node& node) + { + const char_t* name = qualified_name(node); + const char_t* p = find_char(name, ':'); + + return p ? p + 1 : name; + } + + struct namespace_uri_predicate + { + const char_t* prefix; + size_t prefix_length; + + namespace_uri_predicate(const char_t* name) + { + const char_t* pos = find_char(name, ':'); + + prefix = pos ? name : 0; + prefix_length = pos ? static_cast(pos - name) : 0; + } + + bool operator()(xml_attribute a) const + { + const char_t* name = a.name(); + + if (!starts_with(name, PUGIXML_TEXT("xmlns"))) return false; + + return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0; + } + }; + + PUGI__FN const char_t* namespace_uri(xml_node node) + { + namespace_uri_predicate pred = node.name(); + + xml_node p = node; + + while (p) + { + xml_attribute a = p.find_attribute(pred); + + if (a) return a.value(); + + p = p.parent(); + } + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* namespace_uri(xml_attribute attr, xml_node parent) + { + namespace_uri_predicate pred = attr.name(); + + // Default namespace does not apply to attributes + if (!pred.prefix) return PUGIXML_TEXT(""); + + xml_node p = parent; + + while (p) + { + xml_attribute a = p.find_attribute(pred); + + if (a) return a.value(); + + p = p.parent(); + } + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* namespace_uri(const xpath_node& node) + { + return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node()); + } + + PUGI__FN char_t* normalize_space(char_t* buffer) + { + char_t* write = buffer; + + for (char_t* it = buffer; *it; ) + { + char_t ch = *it++; + + if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + // replace whitespace sequence with single space + while (PUGI__IS_CHARTYPE(*it, ct_space)) it++; + + // avoid leading spaces + if (write != buffer) *write++ = ' '; + } + else *write++ = ch; + } + + // remove trailing space + if (write != buffer && PUGI__IS_CHARTYPE(write[-1], ct_space)) write--; + + // zero-terminate + *write = 0; + + return write; + } + + PUGI__FN char_t* translate(char_t* buffer, const char_t* from, const char_t* to, size_t to_length) + { + char_t* write = buffer; + + while (*buffer) + { + PUGI__DMC_VOLATILE char_t ch = *buffer++; + + const char_t* pos = find_char(from, ch); + + if (!pos) + *write++ = ch; // do not process + else if (static_cast(pos - from) < to_length) + *write++ = to[pos - from]; // replace + } + + // zero-terminate + *write = 0; + + return write; + } + + PUGI__FN unsigned char* translate_table_generate(xpath_allocator* alloc, const char_t* from, const char_t* to) + { + unsigned char table[128] = {0}; + + while (*from) + { + unsigned int fc = static_cast(*from); + unsigned int tc = static_cast(*to); + + if (fc >= 128 || tc >= 128) + return 0; + + // code=128 means "skip character" + if (!table[fc]) + table[fc] = static_cast(tc ? tc : 128); + + from++; + if (tc) to++; + } + + for (int i = 0; i < 128; ++i) + if (!table[i]) + table[i] = static_cast(i); + + void* result = alloc->allocate(sizeof(table)); + if (!result) return 0; + + memcpy(result, table, sizeof(table)); + + return static_cast(result); + } + + PUGI__FN char_t* translate_table(char_t* buffer, const unsigned char* table) + { + char_t* write = buffer; + + while (*buffer) + { + char_t ch = *buffer++; + unsigned int index = static_cast(ch); + + if (index < 128) + { + unsigned char code = table[index]; + + // code=128 means "skip character" (table size is 128 so 128 can be a special value) + // this code skips these characters without extra branches + *write = static_cast(code); + write += 1 - (code >> 7); + } + else + { + *write++ = ch; + } + } + + // zero-terminate + *write = 0; + + return write; + } + + inline bool is_xpath_attribute(const char_t* name) + { + return !(starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')); + } + + struct xpath_variable_boolean: xpath_variable + { + xpath_variable_boolean(): xpath_variable(xpath_type_boolean), value(false) + { + } + + bool value; + char_t name[1]; + }; + + struct xpath_variable_number: xpath_variable + { + xpath_variable_number(): xpath_variable(xpath_type_number), value(0) + { + } + + double value; + char_t name[1]; + }; + + struct xpath_variable_string: xpath_variable + { + xpath_variable_string(): xpath_variable(xpath_type_string), value(0) + { + } + + ~xpath_variable_string() + { + if (value) xml_memory::deallocate(value); + } + + char_t* value; + char_t name[1]; + }; + + struct xpath_variable_node_set: xpath_variable + { + xpath_variable_node_set(): xpath_variable(xpath_type_node_set) + { + } + + xpath_node_set value; + char_t name[1]; + }; + + static const xpath_node_set dummy_node_set; + + PUGI__FN PUGI__UNSIGNED_OVERFLOW unsigned int hash_string(const char_t* str) + { + // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time) + unsigned int result = 0; + + while (*str) + { + result += static_cast(*str++); + result += result << 10; + result ^= result >> 6; + } + + result += result << 3; + result ^= result >> 11; + result += result << 15; + + return result; + } + + template PUGI__FN T* new_xpath_variable(const char_t* name) + { + size_t length = strlength(name); + if (length == 0) return 0; // empty variable names are invalid + + // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters + void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); + if (!memory) return 0; + + T* result = new (memory) T(); + + memcpy(result->name, name, (length + 1) * sizeof(char_t)); + + return result; + } + + PUGI__FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) + { + switch (type) + { + case xpath_type_node_set: + return new_xpath_variable(name); + + case xpath_type_number: + return new_xpath_variable(name); + + case xpath_type_string: + return new_xpath_variable(name); + + case xpath_type_boolean: + return new_xpath_variable(name); + + default: + return 0; + } + } + + template PUGI__FN void delete_xpath_variable(T* var) + { + var->~T(); + xml_memory::deallocate(var); + } + + PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) + { + switch (type) + { + case xpath_type_node_set: + delete_xpath_variable(static_cast(var)); + break; + + case xpath_type_number: + delete_xpath_variable(static_cast(var)); + break; + + case xpath_type_string: + delete_xpath_variable(static_cast(var)); + break; + + case xpath_type_boolean: + delete_xpath_variable(static_cast(var)); + break; + + default: + assert(false && "Invalid variable type"); // unreachable + } + } + + PUGI__FN bool copy_xpath_variable(xpath_variable* lhs, const xpath_variable* rhs) + { + switch (rhs->type()) + { + case xpath_type_node_set: + return lhs->set(static_cast(rhs)->value); + + case xpath_type_number: + return lhs->set(static_cast(rhs)->value); + + case xpath_type_string: + return lhs->set(static_cast(rhs)->value); + + case xpath_type_boolean: + return lhs->set(static_cast(rhs)->value); + + default: + assert(false && "Invalid variable type"); // unreachable + return false; + } + } + + PUGI__FN bool get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end, xpath_variable** out_result) + { + size_t length = static_cast(end - begin); + char_t* scratch = buffer; + + if (length >= sizeof(buffer) / sizeof(buffer[0])) + { + // need to make dummy on-heap copy + scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!scratch) return false; + } + + // copy string to zero-terminated buffer and perform lookup + memcpy(scratch, begin, length * sizeof(char_t)); + scratch[length] = 0; + + *out_result = set->get(scratch); + + // free dummy buffer + if (scratch != buffer) xml_memory::deallocate(scratch); + + return true; + } +PUGI__NS_END + +// Internal node set class +PUGI__NS_BEGIN + PUGI__FN xpath_node_set::type_t xpath_get_order(const xpath_node* begin, const xpath_node* end) + { + if (end - begin < 2) + return xpath_node_set::type_sorted; + + document_order_comparator cmp; + + bool first = cmp(begin[0], begin[1]); + + for (const xpath_node* it = begin + 1; it + 1 < end; ++it) + if (cmp(it[0], it[1]) != first) + return xpath_node_set::type_unsorted; + + return first ? xpath_node_set::type_sorted : xpath_node_set::type_sorted_reverse; + } + + PUGI__FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) + { + xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; + + if (type == xpath_node_set::type_unsorted) + { + xpath_node_set::type_t sorted = xpath_get_order(begin, end); + + if (sorted == xpath_node_set::type_unsorted) + { + sort(begin, end, document_order_comparator()); + + type = xpath_node_set::type_sorted; + } + else + type = sorted; + } + + if (type != order) reverse(begin, end); + + return order; + } + + PUGI__FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) + { + if (begin == end) return xpath_node(); + + switch (type) + { + case xpath_node_set::type_sorted: + return *begin; + + case xpath_node_set::type_sorted_reverse: + return *(end - 1); + + case xpath_node_set::type_unsorted: + return *min_element(begin, end, document_order_comparator()); + + default: + assert(false && "Invalid node set type"); // unreachable + return xpath_node(); + } + } + + class xpath_node_set_raw + { + xpath_node_set::type_t _type; + + xpath_node* _begin; + xpath_node* _end; + xpath_node* _eos; + + public: + xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0) + { + } + + xpath_node* begin() const + { + return _begin; + } + + xpath_node* end() const + { + return _end; + } + + bool empty() const + { + return _begin == _end; + } + + size_t size() const + { + return static_cast(_end - _begin); + } + + xpath_node first() const + { + return xpath_first(_begin, _end, _type); + } + + void push_back_grow(const xpath_node& node, xpath_allocator* alloc); + + void push_back(const xpath_node& node, xpath_allocator* alloc) + { + if (_end != _eos) + *_end++ = node; + else + push_back_grow(node, alloc); + } + + void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc) + { + if (begin_ == end_) return; + + size_t size_ = static_cast(_end - _begin); + size_t capacity = static_cast(_eos - _begin); + size_t count = static_cast(end_ - begin_); + + if (size_ + count > capacity) + { + // reallocate the old array or allocate a new one + xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node))); + if (!data) return; + + // finalize + _begin = data; + _end = data + size_; + _eos = data + size_ + count; + } + + memcpy(_end, begin_, count * sizeof(xpath_node)); + _end += count; + } + + void sort_do() + { + _type = xpath_sort(_begin, _end, _type, false); + } + + void truncate(xpath_node* pos) + { + assert(_begin <= pos && pos <= _end); + + _end = pos; + } + + void remove_duplicates(xpath_allocator* alloc) + { + if (_type == xpath_node_set::type_unsorted && _end - _begin > 2) + { + xpath_allocator_capture cr(alloc); + + size_t size_ = static_cast(_end - _begin); + + size_t hash_size = 1; + while (hash_size < size_ + size_ / 2) hash_size *= 2; + + const void** hash_data = static_cast(alloc->allocate(hash_size * sizeof(void**))); + if (!hash_data) return; + + memset(hash_data, 0, hash_size * sizeof(const void**)); + + xpath_node* write = _begin; + + for (xpath_node* it = _begin; it != _end; ++it) + { + const void* attr = it->attribute().internal_object(); + const void* node = it->node().internal_object(); + const void* key = attr ? attr : node; + + if (key && hash_insert(hash_data, hash_size, key)) + { + *write++ = *it; + } + } + + _end = write; + } + else + { + _end = unique(_begin, _end); + } + } + + xpath_node_set::type_t type() const + { + return _type; + } + + void set_type(xpath_node_set::type_t value) + { + _type = value; + } + }; + + PUGI__FN_NO_INLINE void xpath_node_set_raw::push_back_grow(const xpath_node& node, xpath_allocator* alloc) + { + size_t capacity = static_cast(_eos - _begin); + + // get new capacity (1.5x rule) + size_t new_capacity = capacity + capacity / 2 + 1; + + // reallocate the old array or allocate a new one + xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node))); + if (!data) return; + + // finalize + _begin = data; + _end = data + capacity; + _eos = data + new_capacity; + + // push + *_end++ = node; + } +PUGI__NS_END + +PUGI__NS_BEGIN + struct xpath_context + { + xpath_node n; + size_t position, size; + + xpath_context(const xpath_node& n_, size_t position_, size_t size_): n(n_), position(position_), size(size_) + { + } + }; + + enum lexeme_t + { + lex_none = 0, + lex_equal, + lex_not_equal, + lex_less, + lex_greater, + lex_less_or_equal, + lex_greater_or_equal, + lex_plus, + lex_minus, + lex_multiply, + lex_union, + lex_var_ref, + lex_open_brace, + lex_close_brace, + lex_quoted_string, + lex_number, + lex_slash, + lex_double_slash, + lex_open_square_brace, + lex_close_square_brace, + lex_string, + lex_comma, + lex_axis_attribute, + lex_dot, + lex_double_dot, + lex_double_colon, + lex_eof + }; + + struct xpath_lexer_string + { + const char_t* begin; + const char_t* end; + + xpath_lexer_string(): begin(0), end(0) + { + } + + bool operator==(const char_t* other) const + { + size_t length = static_cast(end - begin); + + return strequalrange(other, begin, length); + } + }; + + class xpath_lexer + { + const char_t* _cur; + const char_t* _cur_lexeme_pos; + xpath_lexer_string _cur_lexeme_contents; + + lexeme_t _cur_lexeme; + + public: + explicit xpath_lexer(const char_t* query): _cur(query) + { + next(); + } + + const char_t* state() const + { + return _cur; + } + + void next() + { + const char_t* cur = _cur; + + while (PUGI__IS_CHARTYPE(*cur, ct_space)) ++cur; + + // save lexeme position for error reporting + _cur_lexeme_pos = cur; + + switch (*cur) + { + case 0: + _cur_lexeme = lex_eof; + break; + + case '>': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_greater_or_equal; + } + else + { + cur += 1; + _cur_lexeme = lex_greater; + } + break; + + case '<': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_less_or_equal; + } + else + { + cur += 1; + _cur_lexeme = lex_less; + } + break; + + case '!': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_not_equal; + } + else + { + _cur_lexeme = lex_none; + } + break; + + case '=': + cur += 1; + _cur_lexeme = lex_equal; + + break; + + case '+': + cur += 1; + _cur_lexeme = lex_plus; + + break; + + case '-': + cur += 1; + _cur_lexeme = lex_minus; + + break; + + case '*': + cur += 1; + _cur_lexeme = lex_multiply; + + break; + + case '|': + cur += 1; + _cur_lexeme = lex_union; + + break; + + case '$': + cur += 1; + + if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + + if (cur[0] == ':' && PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // qname + { + cur++; // : + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_var_ref; + } + else + { + _cur_lexeme = lex_none; + } + + break; + + case '(': + cur += 1; + _cur_lexeme = lex_open_brace; + + break; + + case ')': + cur += 1; + _cur_lexeme = lex_close_brace; + + break; + + case '[': + cur += 1; + _cur_lexeme = lex_open_square_brace; + + break; + + case ']': + cur += 1; + _cur_lexeme = lex_close_square_brace; + + break; + + case ',': + cur += 1; + _cur_lexeme = lex_comma; + + break; + + case '/': + if (*(cur+1) == '/') + { + cur += 2; + _cur_lexeme = lex_double_slash; + } + else + { + cur += 1; + _cur_lexeme = lex_slash; + } + break; + + case '.': + if (*(cur+1) == '.') + { + cur += 2; + _cur_lexeme = lex_double_dot; + } + else if (PUGI__IS_CHARTYPEX(*(cur+1), ctx_digit)) + { + _cur_lexeme_contents.begin = cur; // . + + ++cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_number; + } + else + { + cur += 1; + _cur_lexeme = lex_dot; + } + break; + + case '@': + cur += 1; + _cur_lexeme = lex_axis_attribute; + + break; + + case '"': + case '\'': + { + char_t terminator = *cur; + + ++cur; + + _cur_lexeme_contents.begin = cur; + while (*cur && *cur != terminator) cur++; + _cur_lexeme_contents.end = cur; + + if (!*cur) + _cur_lexeme = lex_none; + else + { + cur += 1; + _cur_lexeme = lex_quoted_string; + } + + break; + } + + case ':': + if (*(cur+1) == ':') + { + cur += 2; + _cur_lexeme = lex_double_colon; + } + else + { + _cur_lexeme = lex_none; + } + break; + + default: + if (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + + if (*cur == '.') + { + cur++; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_number; + } + else if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + + if (cur[0] == ':') + { + if (cur[1] == '*') // namespace test ncname:* + { + cur += 2; // :* + } + else if (PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname + { + cur++; // : + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + } + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_string; + } + else + { + _cur_lexeme = lex_none; + } + } + + _cur = cur; + } + + lexeme_t current() const + { + return _cur_lexeme; + } + + const char_t* current_pos() const + { + return _cur_lexeme_pos; + } + + const xpath_lexer_string& contents() const + { + assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string); + + return _cur_lexeme_contents; + } + }; + + enum ast_type_t + { + ast_unknown, + ast_op_or, // left or right + ast_op_and, // left and right + ast_op_equal, // left = right + ast_op_not_equal, // left != right + ast_op_less, // left < right + ast_op_greater, // left > right + ast_op_less_or_equal, // left <= right + ast_op_greater_or_equal, // left >= right + ast_op_add, // left + right + ast_op_subtract, // left - right + ast_op_multiply, // left * right + ast_op_divide, // left / right + ast_op_mod, // left % right + ast_op_negate, // left - right + ast_op_union, // left | right + ast_predicate, // apply predicate to set; next points to next predicate + ast_filter, // select * from left where right + ast_string_constant, // string constant + ast_number_constant, // number constant + ast_variable, // variable + ast_func_last, // last() + ast_func_position, // position() + ast_func_count, // count(left) + ast_func_id, // id(left) + ast_func_local_name_0, // local-name() + ast_func_local_name_1, // local-name(left) + ast_func_namespace_uri_0, // namespace-uri() + ast_func_namespace_uri_1, // namespace-uri(left) + ast_func_name_0, // name() + ast_func_name_1, // name(left) + ast_func_string_0, // string() + ast_func_string_1, // string(left) + ast_func_concat, // concat(left, right, siblings) + ast_func_starts_with, // starts_with(left, right) + ast_func_contains, // contains(left, right) + ast_func_substring_before, // substring-before(left, right) + ast_func_substring_after, // substring-after(left, right) + ast_func_substring_2, // substring(left, right) + ast_func_substring_3, // substring(left, right, third) + ast_func_string_length_0, // string-length() + ast_func_string_length_1, // string-length(left) + ast_func_normalize_space_0, // normalize-space() + ast_func_normalize_space_1, // normalize-space(left) + ast_func_translate, // translate(left, right, third) + ast_func_boolean, // boolean(left) + ast_func_not, // not(left) + ast_func_true, // true() + ast_func_false, // false() + ast_func_lang, // lang(left) + ast_func_number_0, // number() + ast_func_number_1, // number(left) + ast_func_sum, // sum(left) + ast_func_floor, // floor(left) + ast_func_ceiling, // ceiling(left) + ast_func_round, // round(left) + ast_step, // process set left with step + ast_step_root, // select root node + + ast_opt_translate_table, // translate(left, right, third) where right/third are constants + ast_opt_compare_attribute // @name = 'string' + }; + + enum axis_t + { + axis_ancestor, + axis_ancestor_or_self, + axis_attribute, + axis_child, + axis_descendant, + axis_descendant_or_self, + axis_following, + axis_following_sibling, + axis_namespace, + axis_parent, + axis_preceding, + axis_preceding_sibling, + axis_self + }; + + enum nodetest_t + { + nodetest_none, + nodetest_name, + nodetest_type_node, + nodetest_type_comment, + nodetest_type_pi, + nodetest_type_text, + nodetest_pi, + nodetest_all, + nodetest_all_in_namespace + }; + + enum predicate_t + { + predicate_default, + predicate_posinv, + predicate_constant, + predicate_constant_one + }; + + enum nodeset_eval_t + { + nodeset_eval_all, + nodeset_eval_any, + nodeset_eval_first + }; + + template struct axis_to_type + { + static const axis_t axis; + }; + + template const axis_t axis_to_type::axis = N; + + class xpath_ast_node + { + private: + // node type + char _type; + char _rettype; + + // for ast_step + char _axis; + + // for ast_step/ast_predicate/ast_filter + char _test; + + // tree node structure + xpath_ast_node* _left; + xpath_ast_node* _right; + xpath_ast_node* _next; + + union + { + // value for ast_string_constant + const char_t* string; + // value for ast_number_constant + double number; + // variable for ast_variable + xpath_variable* variable; + // node test for ast_step (node name/namespace/node type/pi target) + const char_t* nodetest; + // table for ast_opt_translate_table + const unsigned char* table; + } _data; + + xpath_ast_node(const xpath_ast_node&); + xpath_ast_node& operator=(const xpath_ast_node&); + + template static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) + { + xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); + + if (lt != xpath_type_node_set && rt != xpath_type_node_set) + { + if (lt == xpath_type_boolean || rt == xpath_type_boolean) + return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); + else if (lt == xpath_type_number || rt == xpath_type_number) + return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); + else if (lt == xpath_type_string || rt == xpath_type_string) + { + xpath_allocator_capture cr(stack.result); + + xpath_string ls = lhs->eval_string(c, stack); + xpath_string rs = rhs->eval_string(c, stack); + + return comp(ls, rs); + } + } + else if (lt == xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(string_value(*li, stack.result), string_value(*ri, stack.result))) + return true; + } + + return false; + } + else + { + if (lt == xpath_type_node_set) + { + swap(lhs, rhs); + swap(lt, rt); + } + + if (lt == xpath_type_boolean) + return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); + else if (lt == xpath_type_number) + { + xpath_allocator_capture cr(stack.result); + + double l = lhs->eval_number(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + + return false; + } + else if (lt == xpath_type_string) + { + xpath_allocator_capture cr(stack.result); + + xpath_string l = lhs->eval_string(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, string_value(*ri, stack.result))) + return true; + } + + return false; + } + } + + assert(false && "Wrong types"); // unreachable + return false; + } + + static bool eval_once(xpath_node_set::type_t type, nodeset_eval_t eval) + { + return type == xpath_node_set::type_sorted ? eval != nodeset_eval_all : eval == nodeset_eval_any; + } + + template static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) + { + xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); + + if (lt != xpath_type_node_set && rt != xpath_type_node_set) + return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); + else if (lt == xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + { + xpath_allocator_capture cri(stack.result); + + double l = convert_string_to_number(string_value(*li, stack.result).c_str()); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture crii(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + } + + return false; + } + else if (lt != xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + double l = lhs->eval_number(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + + return false; + } + else if (lt == xpath_type_node_set && rt != xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all); + double r = rhs->eval_number(c, stack); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + { + xpath_allocator_capture cri(stack.result); + + if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r)) + return true; + } + + return false; + } + else + { + assert(false && "Wrong types"); // unreachable + return false; + } + } + + static void apply_predicate_boolean(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack, bool once) + { + assert(ns.size() >= first); + assert(expr->rettype() != xpath_type_number); + + size_t i = 1; + size_t size = ns.size() - first; + + xpath_node* last = ns.begin() + first; + + // remove_if... or well, sort of + for (xpath_node* it = last; it != ns.end(); ++it, ++i) + { + xpath_context c(*it, i, size); + + if (expr->eval_boolean(c, stack)) + { + *last++ = *it; + + if (once) break; + } + } + + ns.truncate(last); + } + + static void apply_predicate_number(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack, bool once) + { + assert(ns.size() >= first); + assert(expr->rettype() == xpath_type_number); + + size_t i = 1; + size_t size = ns.size() - first; + + xpath_node* last = ns.begin() + first; + + // remove_if... or well, sort of + for (xpath_node* it = last; it != ns.end(); ++it, ++i) + { + xpath_context c(*it, i, size); + + if (expr->eval_number(c, stack) == static_cast(i)) + { + *last++ = *it; + + if (once) break; + } + } + + ns.truncate(last); + } + + static void apply_predicate_number_const(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack) + { + assert(ns.size() >= first); + assert(expr->rettype() == xpath_type_number); + + size_t size = ns.size() - first; + + xpath_node* last = ns.begin() + first; + + xpath_context c(xpath_node(), 1, size); + + double er = expr->eval_number(c, stack); + + if (er >= 1.0 && er <= static_cast(size)) + { + size_t eri = static_cast(er); + + if (er == static_cast(eri)) + { + xpath_node r = last[eri - 1]; + + *last++ = r; + } + } + + ns.truncate(last); + } + + void apply_predicate(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack, bool once) + { + if (ns.size() == first) return; + + assert(_type == ast_filter || _type == ast_predicate); + + if (_test == predicate_constant || _test == predicate_constant_one) + apply_predicate_number_const(ns, first, _right, stack); + else if (_right->rettype() == xpath_type_number) + apply_predicate_number(ns, first, _right, stack, once); + else + apply_predicate_boolean(ns, first, _right, stack, once); + } + + void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack, nodeset_eval_t eval) + { + if (ns.size() == first) return; + + bool last_once = eval_once(ns.type(), eval); + + for (xpath_ast_node* pred = _right; pred; pred = pred->_next) + pred->apply_predicate(ns, first, stack, !pred->_next && last_once); + } + + bool step_push(xpath_node_set_raw& ns, xml_attribute_struct* a, xml_node_struct* parent, xpath_allocator* alloc) + { + assert(a); + + const char_t* name = a->name ? a->name + 0 : PUGIXML_TEXT(""); + + switch (_test) + { + case nodetest_name: + if (strequal(name, _data.nodetest) && is_xpath_attribute(name)) + { + ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); + return true; + } + break; + + case nodetest_type_node: + case nodetest_all: + if (is_xpath_attribute(name)) + { + ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); + return true; + } + break; + + case nodetest_all_in_namespace: + if (starts_with(name, _data.nodetest) && is_xpath_attribute(name)) + { + ns.push_back(xpath_node(xml_attribute(a), xml_node(parent)), alloc); + return true; + } + break; + + default: + ; + } + + return false; + } + + bool step_push(xpath_node_set_raw& ns, xml_node_struct* n, xpath_allocator* alloc) + { + assert(n); + + xml_node_type type = PUGI__NODETYPE(n); + + switch (_test) + { + case nodetest_name: + if (type == node_element && n->name && strequal(n->name, _data.nodetest)) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_type_node: + ns.push_back(xml_node(n), alloc); + return true; + + case nodetest_type_comment: + if (type == node_comment) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_type_text: + if (type == node_pcdata || type == node_cdata) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_type_pi: + if (type == node_pi) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_pi: + if (type == node_pi && n->name && strequal(n->name, _data.nodetest)) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_all: + if (type == node_element) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + case nodetest_all_in_namespace: + if (type == node_element && n->name && starts_with(n->name, _data.nodetest)) + { + ns.push_back(xml_node(n), alloc); + return true; + } + break; + + default: + assert(false && "Unknown axis"); // unreachable + } + + return false; + } + + template void step_fill(xpath_node_set_raw& ns, xml_node_struct* n, xpath_allocator* alloc, bool once, T) + { + const axis_t axis = T::axis; + + switch (axis) + { + case axis_attribute: + { + for (xml_attribute_struct* a = n->first_attribute; a; a = a->next_attribute) + if (step_push(ns, a, n, alloc) & once) + return; + + break; + } + + case axis_child: + { + for (xml_node_struct* c = n->first_child; c; c = c->next_sibling) + if (step_push(ns, c, alloc) & once) + return; + + break; + } + + case axis_descendant: + case axis_descendant_or_self: + { + if (axis == axis_descendant_or_self) + if (step_push(ns, n, alloc) & once) + return; + + xml_node_struct* cur = n->first_child; + + while (cur) + { + if (step_push(ns, cur, alloc) & once) + return; + + if (cur->first_child) + cur = cur->first_child; + else + { + while (!cur->next_sibling) + { + cur = cur->parent; + + if (cur == n) return; + } + + cur = cur->next_sibling; + } + } + + break; + } + + case axis_following_sibling: + { + for (xml_node_struct* c = n->next_sibling; c; c = c->next_sibling) + if (step_push(ns, c, alloc) & once) + return; + + break; + } + + case axis_preceding_sibling: + { + for (xml_node_struct* c = n->prev_sibling_c; c->next_sibling; c = c->prev_sibling_c) + if (step_push(ns, c, alloc) & once) + return; + + break; + } + + case axis_following: + { + xml_node_struct* cur = n; + + // exit from this node so that we don't include descendants + while (!cur->next_sibling) + { + cur = cur->parent; + + if (!cur) return; + } + + cur = cur->next_sibling; + + while (cur) + { + if (step_push(ns, cur, alloc) & once) + return; + + if (cur->first_child) + cur = cur->first_child; + else + { + while (!cur->next_sibling) + { + cur = cur->parent; + + if (!cur) return; + } + + cur = cur->next_sibling; + } + } + + break; + } + + case axis_preceding: + { + xml_node_struct* cur = n; + + // exit from this node so that we don't include descendants + while (!cur->prev_sibling_c->next_sibling) + { + cur = cur->parent; + + if (!cur) return; + } + + cur = cur->prev_sibling_c; + + while (cur) + { + if (cur->first_child) + cur = cur->first_child->prev_sibling_c; + else + { + // leaf node, can't be ancestor + if (step_push(ns, cur, alloc) & once) + return; + + while (!cur->prev_sibling_c->next_sibling) + { + cur = cur->parent; + + if (!cur) return; + + if (!node_is_ancestor(cur, n)) + if (step_push(ns, cur, alloc) & once) + return; + } + + cur = cur->prev_sibling_c; + } + } + + break; + } + + case axis_ancestor: + case axis_ancestor_or_self: + { + if (axis == axis_ancestor_or_self) + if (step_push(ns, n, alloc) & once) + return; + + xml_node_struct* cur = n->parent; + + while (cur) + { + if (step_push(ns, cur, alloc) & once) + return; + + cur = cur->parent; + } + + break; + } + + case axis_self: + { + step_push(ns, n, alloc); + + break; + } + + case axis_parent: + { + if (n->parent) + step_push(ns, n->parent, alloc); + + break; + } + + default: + assert(false && "Unimplemented axis"); // unreachable + } + } + + template void step_fill(xpath_node_set_raw& ns, xml_attribute_struct* a, xml_node_struct* p, xpath_allocator* alloc, bool once, T v) + { + const axis_t axis = T::axis; + + switch (axis) + { + case axis_ancestor: + case axis_ancestor_or_self: + { + if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test + if (step_push(ns, a, p, alloc) & once) + return; + + xml_node_struct* cur = p; + + while (cur) + { + if (step_push(ns, cur, alloc) & once) + return; + + cur = cur->parent; + } + + break; + } + + case axis_descendant_or_self: + case axis_self: + { + if (_test == nodetest_type_node) // reject attributes based on principal node type test + step_push(ns, a, p, alloc); + + break; + } + + case axis_following: + { + xml_node_struct* cur = p; + + while (cur) + { + if (cur->first_child) + cur = cur->first_child; + else + { + while (!cur->next_sibling) + { + cur = cur->parent; + + if (!cur) return; + } + + cur = cur->next_sibling; + } + + if (step_push(ns, cur, alloc) & once) + return; + } + + break; + } + + case axis_parent: + { + step_push(ns, p, alloc); + + break; + } + + case axis_preceding: + { + // preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding + step_fill(ns, p, alloc, once, v); + break; + } + + default: + assert(false && "Unimplemented axis"); // unreachable + } + } + + template void step_fill(xpath_node_set_raw& ns, const xpath_node& xn, xpath_allocator* alloc, bool once, T v) + { + const axis_t axis = T::axis; + const bool axis_has_attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self); + + if (xn.node()) + step_fill(ns, xn.node().internal_object(), alloc, once, v); + else if (axis_has_attributes && xn.attribute() && xn.parent()) + step_fill(ns, xn.attribute().internal_object(), xn.parent().internal_object(), alloc, once, v); + } + + template xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval, T v) + { + const axis_t axis = T::axis; + const bool axis_reverse = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling); + const xpath_node_set::type_t axis_type = axis_reverse ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; + + bool once = + (axis == axis_attribute && _test == nodetest_name) || + (!_right && eval_once(axis_type, eval)) || + // coverity[mixed_enums] + (_right && !_right->_next && _right->_test == predicate_constant_one); + + xpath_node_set_raw ns; + ns.set_type(axis_type); + + if (_left) + { + xpath_node_set_raw s = _left->eval_node_set(c, stack, nodeset_eval_all); + + // self axis preserves the original order + if (axis == axis_self) ns.set_type(s.type()); + + for (const xpath_node* it = s.begin(); it != s.end(); ++it) + { + size_t size = ns.size(); + + // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes + if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted); + + step_fill(ns, *it, stack.result, once, v); + if (_right) apply_predicates(ns, size, stack, eval); + } + } + else + { + step_fill(ns, c.n, stack.result, once, v); + if (_right) apply_predicates(ns, 0, stack, eval); + } + + // child, attribute and self axes always generate unique set of nodes + // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice + if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted) + ns.remove_duplicates(stack.temp); + + return ns; + } + + public: + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_string_constant); + _data.string = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_number_constant); + _data.number = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_variable); + _data.variable = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0) + { + } + + xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents): + _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(static_cast(axis)), _test(static_cast(test)), _left(left), _right(0), _next(0) + { + assert(type == ast_step); + _data.nodetest = contents; + } + + xpath_ast_node(ast_type_t type, xpath_ast_node* left, xpath_ast_node* right, predicate_t test): + _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(0), _test(static_cast(test)), _left(left), _right(right), _next(0) + { + assert(type == ast_filter || type == ast_predicate); + } + + void set_next(xpath_ast_node* value) + { + _next = value; + } + + void set_right(xpath_ast_node* value) + { + _right = value; + } + + bool eval_boolean(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_op_or: + return _left->eval_boolean(c, stack) || _right->eval_boolean(c, stack); + + case ast_op_and: + return _left->eval_boolean(c, stack) && _right->eval_boolean(c, stack); + + case ast_op_equal: + return compare_eq(_left, _right, c, stack, equal_to()); + + case ast_op_not_equal: + return compare_eq(_left, _right, c, stack, not_equal_to()); + + case ast_op_less: + return compare_rel(_left, _right, c, stack, less()); + + case ast_op_greater: + return compare_rel(_right, _left, c, stack, less()); + + case ast_op_less_or_equal: + return compare_rel(_left, _right, c, stack, less_equal()); + + case ast_op_greater_or_equal: + return compare_rel(_right, _left, c, stack, less_equal()); + + case ast_func_starts_with: + { + xpath_allocator_capture cr(stack.result); + + xpath_string lr = _left->eval_string(c, stack); + xpath_string rr = _right->eval_string(c, stack); + + return starts_with(lr.c_str(), rr.c_str()); + } + + case ast_func_contains: + { + xpath_allocator_capture cr(stack.result); + + xpath_string lr = _left->eval_string(c, stack); + xpath_string rr = _right->eval_string(c, stack); + + return find_substring(lr.c_str(), rr.c_str()) != 0; + } + + case ast_func_boolean: + return _left->eval_boolean(c, stack); + + case ast_func_not: + return !_left->eval_boolean(c, stack); + + case ast_func_true: + return true; + + case ast_func_false: + return false; + + case ast_func_lang: + { + if (c.n.attribute()) return false; + + xpath_allocator_capture cr(stack.result); + + xpath_string lang = _left->eval_string(c, stack); + + for (xml_node n = c.n.node(); n; n = n.parent()) + { + xml_attribute a = n.attribute(PUGIXML_TEXT("xml:lang")); + + if (a) + { + const char_t* value = a.value(); + + // strnicmp / strncasecmp is not portable + for (const char_t* lit = lang.c_str(); *lit; ++lit) + { + if (tolower_ascii(*lit) != tolower_ascii(*value)) return false; + ++value; + } + + return *value == 0 || *value == '-'; + } + } + + return false; + } + + case ast_opt_compare_attribute: + { + const char_t* value = (_right->_type == ast_string_constant) ? _right->_data.string : _right->_data.variable->get_string(); + + xml_attribute attr = c.n.node().attribute(_left->_data.nodetest); + + return attr && strequal(attr.value(), value) && is_xpath_attribute(attr.name()); + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_boolean) + return _data.variable->get_boolean(); + + // variable needs to be converted to the correct type, this is handled by the fallthrough block below + break; + } + + default: + ; + } + + // none of the ast types that return the value directly matched, we need to perform type conversion + switch (_rettype) + { + case xpath_type_number: + return convert_number_to_boolean(eval_number(c, stack)); + + case xpath_type_string: + { + xpath_allocator_capture cr(stack.result); + + return !eval_string(c, stack).empty(); + } + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.result); + + return !eval_node_set(c, stack, nodeset_eval_any).empty(); + } + + default: + assert(false && "Wrong expression for return type boolean"); // unreachable + return false; + } + } + + double eval_number(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_op_add: + return _left->eval_number(c, stack) + _right->eval_number(c, stack); + + case ast_op_subtract: + return _left->eval_number(c, stack) - _right->eval_number(c, stack); + + case ast_op_multiply: + return _left->eval_number(c, stack) * _right->eval_number(c, stack); + + case ast_op_divide: + return _left->eval_number(c, stack) / _right->eval_number(c, stack); + + case ast_op_mod: + return fmod(_left->eval_number(c, stack), _right->eval_number(c, stack)); + + case ast_op_negate: + return -_left->eval_number(c, stack); + + case ast_number_constant: + return _data.number; + + case ast_func_last: + return static_cast(c.size); + + case ast_func_position: + return static_cast(c.position); + + case ast_func_count: + { + xpath_allocator_capture cr(stack.result); + + return static_cast(_left->eval_node_set(c, stack, nodeset_eval_all).size()); + } + + case ast_func_string_length_0: + { + xpath_allocator_capture cr(stack.result); + + return static_cast(string_value(c.n, stack.result).length()); + } + + case ast_func_string_length_1: + { + xpath_allocator_capture cr(stack.result); + + return static_cast(_left->eval_string(c, stack).length()); + } + + case ast_func_number_0: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(string_value(c.n, stack.result).c_str()); + } + + case ast_func_number_1: + return _left->eval_number(c, stack); + + case ast_func_sum: + { + xpath_allocator_capture cr(stack.result); + + double r = 0; + + xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_all); + + for (const xpath_node* it = ns.begin(); it != ns.end(); ++it) + { + xpath_allocator_capture cri(stack.result); + + r += convert_string_to_number(string_value(*it, stack.result).c_str()); + } + + return r; + } + + case ast_func_floor: + { + double r = _left->eval_number(c, stack); + + return r == r ? floor(r) : r; + } + + case ast_func_ceiling: + { + double r = _left->eval_number(c, stack); + + return r == r ? ceil(r) : r; + } + + case ast_func_round: + return round_nearest_nzero(_left->eval_number(c, stack)); + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_number) + return _data.variable->get_number(); + + // variable needs to be converted to the correct type, this is handled by the fallthrough block below + break; + } + + default: + ; + } + + // none of the ast types that return the value directly matched, we need to perform type conversion + switch (_rettype) + { + case xpath_type_boolean: + return eval_boolean(c, stack) ? 1 : 0; + + case xpath_type_string: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(eval_string(c, stack).c_str()); + } + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(eval_string(c, stack).c_str()); + } + + default: + assert(false && "Wrong expression for return type number"); // unreachable + return 0; + } + } + + xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack) + { + assert(_type == ast_func_concat); + + xpath_allocator_capture ct(stack.temp); + + // count the string number + size_t count = 1; + for (xpath_ast_node* nc = _right; nc; nc = nc->_next) count++; + + // allocate a buffer for temporary string objects + xpath_string* buffer = static_cast(stack.temp->allocate(count * sizeof(xpath_string))); + if (!buffer) return xpath_string(); + + // evaluate all strings to temporary stack + xpath_stack swapped_stack = {stack.temp, stack.result}; + + buffer[0] = _left->eval_string(c, swapped_stack); + + size_t pos = 1; + for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) buffer[pos] = n->eval_string(c, swapped_stack); + assert(pos == count); + + // get total length + size_t length = 0; + for (size_t i = 0; i < count; ++i) length += buffer[i].length(); + + // create final string + char_t* result = static_cast(stack.result->allocate((length + 1) * sizeof(char_t))); + if (!result) return xpath_string(); + + char_t* ri = result; + + for (size_t j = 0; j < count; ++j) + for (const char_t* bi = buffer[j].c_str(); *bi; ++bi) + *ri++ = *bi; + + *ri = 0; + + return xpath_string::from_heap_preallocated(result, ri); + } + + xpath_string eval_string(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_string_constant: + return xpath_string::from_const(_data.string); + + case ast_func_local_name_0: + { + xpath_node na = c.n; + + return xpath_string::from_const(local_name(na)); + } + + case ast_func_local_name_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); + xpath_node na = ns.first(); + + return xpath_string::from_const(local_name(na)); + } + + case ast_func_name_0: + { + xpath_node na = c.n; + + return xpath_string::from_const(qualified_name(na)); + } + + case ast_func_name_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); + xpath_node na = ns.first(); + + return xpath_string::from_const(qualified_name(na)); + } + + case ast_func_namespace_uri_0: + { + xpath_node na = c.n; + + return xpath_string::from_const(namespace_uri(na)); + } + + case ast_func_namespace_uri_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first); + xpath_node na = ns.first(); + + return xpath_string::from_const(namespace_uri(na)); + } + + case ast_func_string_0: + return string_value(c.n, stack.result); + + case ast_func_string_1: + return _left->eval_string(c, stack); + + case ast_func_concat: + return eval_string_concat(c, stack); + + case ast_func_substring_before: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + xpath_string p = _right->eval_string(c, swapped_stack); + + const char_t* pos = find_substring(s.c_str(), p.c_str()); + + return pos ? xpath_string::from_heap(s.c_str(), pos, stack.result) : xpath_string(); + } + + case ast_func_substring_after: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + xpath_string p = _right->eval_string(c, swapped_stack); + + const char_t* pos = find_substring(s.c_str(), p.c_str()); + if (!pos) return xpath_string(); + + const char_t* rbegin = pos + p.length(); + const char_t* rend = s.c_str() + s.length(); + + return s.uses_heap() ? xpath_string::from_heap(rbegin, rend, stack.result) : xpath_string::from_const(rbegin); + } + + case ast_func_substring_2: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + size_t s_length = s.length(); + + double first = round_nearest(_right->eval_number(c, stack)); + + if (is_nan(first)) return xpath_string(); // NaN + else if (first >= static_cast(s_length + 1)) return xpath_string(); + + size_t pos = first < 1 ? 1 : static_cast(first); + assert(1 <= pos && pos <= s_length + 1); + + const char_t* rbegin = s.c_str() + (pos - 1); + const char_t* rend = s.c_str() + s.length(); + + return s.uses_heap() ? xpath_string::from_heap(rbegin, rend, stack.result) : xpath_string::from_const(rbegin); + } + + case ast_func_substring_3: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + size_t s_length = s.length(); + + double first = round_nearest(_right->eval_number(c, stack)); + double last = first + round_nearest(_right->_next->eval_number(c, stack)); + + if (is_nan(first) || is_nan(last)) return xpath_string(); + else if (first >= static_cast(s_length + 1)) return xpath_string(); + else if (first >= last) return xpath_string(); + else if (last < 1) return xpath_string(); + + size_t pos = first < 1 ? 1 : static_cast(first); + size_t end = last >= static_cast(s_length + 1) ? s_length + 1 : static_cast(last); + + assert(1 <= pos && pos <= end && end <= s_length + 1); + const char_t* rbegin = s.c_str() + (pos - 1); + const char_t* rend = s.c_str() + (end - 1); + + return (end == s_length + 1 && !s.uses_heap()) ? xpath_string::from_const(rbegin) : xpath_string::from_heap(rbegin, rend, stack.result); + } + + case ast_func_normalize_space_0: + { + xpath_string s = string_value(c.n, stack.result); + + char_t* begin = s.data(stack.result); + if (!begin) return xpath_string(); + + char_t* end = normalize_space(begin); + + return xpath_string::from_heap_preallocated(begin, end); + } + + case ast_func_normalize_space_1: + { + xpath_string s = _left->eval_string(c, stack); + + char_t* begin = s.data(stack.result); + if (!begin) return xpath_string(); + + char_t* end = normalize_space(begin); + + return xpath_string::from_heap_preallocated(begin, end); + } + + case ast_func_translate: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, stack); + xpath_string from = _right->eval_string(c, swapped_stack); + xpath_string to = _right->_next->eval_string(c, swapped_stack); + + char_t* begin = s.data(stack.result); + if (!begin) return xpath_string(); + + char_t* end = translate(begin, from.c_str(), to.c_str(), to.length()); + + return xpath_string::from_heap_preallocated(begin, end); + } + + case ast_opt_translate_table: + { + xpath_string s = _left->eval_string(c, stack); + + char_t* begin = s.data(stack.result); + if (!begin) return xpath_string(); + + char_t* end = translate_table(begin, _data.table); + + return xpath_string::from_heap_preallocated(begin, end); + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_string) + return xpath_string::from_const(_data.variable->get_string()); + + // variable needs to be converted to the correct type, this is handled by the fallthrough block below + break; + } + + default: + ; + } + + // none of the ast types that return the value directly matched, we need to perform type conversion + switch (_rettype) + { + case xpath_type_boolean: + return xpath_string::from_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); + + case xpath_type_number: + return convert_number_to_string(eval_number(c, stack), stack.result); + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_node_set_raw ns = eval_node_set(c, swapped_stack, nodeset_eval_first); + return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result); + } + + default: + assert(false && "Wrong expression for return type string"); // unreachable + return xpath_string(); + } + } + + xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval) + { + switch (_type) + { + case ast_op_union: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_node_set_raw ls = _left->eval_node_set(c, stack, eval); + xpath_node_set_raw rs = _right->eval_node_set(c, swapped_stack, eval); + + // we can optimize merging two sorted sets, but this is a very rare operation, so don't bother + ls.set_type(xpath_node_set::type_unsorted); + + ls.append(rs.begin(), rs.end(), stack.result); + ls.remove_duplicates(stack.temp); + + return ls; + } + + case ast_filter: + { + xpath_node_set_raw set = _left->eval_node_set(c, stack, _test == predicate_constant_one ? nodeset_eval_first : nodeset_eval_all); + + // either expression is a number or it contains position() call; sort by document order + if (_test != predicate_posinv) set.sort_do(); + + bool once = eval_once(set.type(), eval); + + apply_predicate(set, 0, stack, once); + + return set; + } + + case ast_func_id: + return xpath_node_set_raw(); + + case ast_step: + { + switch (_axis) + { + case axis_ancestor: + return step_do(c, stack, eval, axis_to_type()); + + case axis_ancestor_or_self: + return step_do(c, stack, eval, axis_to_type()); + + case axis_attribute: + return step_do(c, stack, eval, axis_to_type()); + + case axis_child: + return step_do(c, stack, eval, axis_to_type()); + + case axis_descendant: + return step_do(c, stack, eval, axis_to_type()); + + case axis_descendant_or_self: + return step_do(c, stack, eval, axis_to_type()); + + case axis_following: + return step_do(c, stack, eval, axis_to_type()); + + case axis_following_sibling: + return step_do(c, stack, eval, axis_to_type()); + + case axis_namespace: + // namespaced axis is not supported + return xpath_node_set_raw(); + + case axis_parent: + return step_do(c, stack, eval, axis_to_type()); + + case axis_preceding: + return step_do(c, stack, eval, axis_to_type()); + + case axis_preceding_sibling: + return step_do(c, stack, eval, axis_to_type()); + + case axis_self: + return step_do(c, stack, eval, axis_to_type()); + + default: + assert(false && "Unknown axis"); // unreachable + return xpath_node_set_raw(); + } + } + + case ast_step_root: + { + assert(!_right); // root step can't have any predicates + + xpath_node_set_raw ns; + + ns.set_type(xpath_node_set::type_sorted); + + if (c.n.node()) ns.push_back(c.n.node().root(), stack.result); + else if (c.n.attribute()) ns.push_back(c.n.parent().root(), stack.result); + + return ns; + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_node_set) + { + const xpath_node_set& s = _data.variable->get_node_set(); + + xpath_node_set_raw ns; + + ns.set_type(s.type()); + ns.append(s.begin(), s.end(), stack.result); + + return ns; + } + + // variable needs to be converted to the correct type, this is handled by the fallthrough block below + break; + } + + default: + ; + } + + // none of the ast types that return the value directly matched, but conversions to node set are invalid + assert(false && "Wrong expression for return type node set"); // unreachable + return xpath_node_set_raw(); + } + + void optimize(xpath_allocator* alloc) + { + if (_left) + _left->optimize(alloc); + + if (_right) + _right->optimize(alloc); + + if (_next) + _next->optimize(alloc); + + // coverity[var_deref_model] + optimize_self(alloc); + } + + void optimize_self(xpath_allocator* alloc) + { + // Rewrite [position()=expr] with [expr] + // Note that this step has to go before classification to recognize [position()=1] + if ((_type == ast_filter || _type == ast_predicate) && + _right && // workaround for clang static analyzer (_right is never null for ast_filter/ast_predicate) + _right->_type == ast_op_equal && _right->_left->_type == ast_func_position && _right->_right->_rettype == xpath_type_number) + { + _right = _right->_right; + } + + // Classify filter/predicate ops to perform various optimizations during evaluation + if ((_type == ast_filter || _type == ast_predicate) && _right) // workaround for clang static analyzer (_right is never null for ast_filter/ast_predicate) + { + assert(_test == predicate_default); + + if (_right->_type == ast_number_constant && _right->_data.number == 1.0) + _test = predicate_constant_one; + else if (_right->_rettype == xpath_type_number && (_right->_type == ast_number_constant || _right->_type == ast_variable || _right->_type == ast_func_last)) + _test = predicate_constant; + else if (_right->_rettype != xpath_type_number && _right->is_posinv_expr()) + _test = predicate_posinv; + } + + // Rewrite descendant-or-self::node()/child::foo with descendant::foo + // The former is a full form of //foo, the latter is much faster since it executes the node test immediately + // Do a similar kind of rewrite for self/descendant/descendant-or-self axes + // Note that we only rewrite positionally invariant steps (//foo[1] != /descendant::foo[1]) + if (_type == ast_step && (_axis == axis_child || _axis == axis_self || _axis == axis_descendant || _axis == axis_descendant_or_self) && + _left && _left->_type == ast_step && _left->_axis == axis_descendant_or_self && _left->_test == nodetest_type_node && !_left->_right && + is_posinv_step()) + { + if (_axis == axis_child || _axis == axis_descendant) + _axis = axis_descendant; + else + _axis = axis_descendant_or_self; + + _left = _left->_left; + } + + // Use optimized lookup table implementation for translate() with constant arguments + if (_type == ast_func_translate && + _right && // workaround for clang static analyzer (_right is never null for ast_func_translate) + _right->_type == ast_string_constant && _right->_next->_type == ast_string_constant) + { + unsigned char* table = translate_table_generate(alloc, _right->_data.string, _right->_next->_data.string); + + if (table) + { + _type = ast_opt_translate_table; + _data.table = table; + } + } + + // Use optimized path for @attr = 'value' or @attr = $value + if (_type == ast_op_equal && + _left && _right && // workaround for clang static analyzer and Coverity (_left and _right are never null for ast_op_equal) + // coverity[mixed_enums] + _left->_type == ast_step && _left->_axis == axis_attribute && _left->_test == nodetest_name && !_left->_left && !_left->_right && + (_right->_type == ast_string_constant || (_right->_type == ast_variable && _right->_rettype == xpath_type_string))) + { + _type = ast_opt_compare_attribute; + } + } + + bool is_posinv_expr() const + { + switch (_type) + { + case ast_func_position: + case ast_func_last: + return false; + + case ast_string_constant: + case ast_number_constant: + case ast_variable: + return true; + + case ast_step: + case ast_step_root: + return true; + + case ast_predicate: + case ast_filter: + return true; + + default: + if (_left && !_left->is_posinv_expr()) return false; + + for (xpath_ast_node* n = _right; n; n = n->_next) + if (!n->is_posinv_expr()) return false; + + return true; + } + } + + bool is_posinv_step() const + { + assert(_type == ast_step); + + for (xpath_ast_node* n = _right; n; n = n->_next) + { + assert(n->_type == ast_predicate); + + if (n->_test != predicate_posinv) + return false; + } + + return true; + } + + xpath_value_type rettype() const + { + return static_cast(_rettype); + } + }; + + static const size_t xpath_ast_depth_limit = + #ifdef PUGIXML_XPATH_DEPTH_LIMIT + PUGIXML_XPATH_DEPTH_LIMIT + #else + 1024 + #endif + ; + + struct xpath_parser + { + xpath_allocator* _alloc; + xpath_lexer _lexer; + + const char_t* _query; + xpath_variable_set* _variables; + + xpath_parse_result* _result; + + char_t _scratch[32]; + + size_t _depth; + + xpath_ast_node* error(const char* message) + { + _result->error = message; + _result->offset = _lexer.current_pos() - _query; + + return 0; + } + + xpath_ast_node* error_oom() + { + assert(_alloc->_error); + *_alloc->_error = true; + + return 0; + } + + xpath_ast_node* error_rec() + { + return error("Exceeded maximum allowed query depth"); + } + + void* alloc_node() + { + return _alloc->allocate(sizeof(xpath_ast_node)); + } + + xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, const char_t* value) + { + void* memory = alloc_node(); + return memory ? new (memory) xpath_ast_node(type, rettype, value) : 0; + } + + xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, double value) + { + void* memory = alloc_node(); + return memory ? new (memory) xpath_ast_node(type, rettype, value) : 0; + } + + xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, xpath_variable* value) + { + void* memory = alloc_node(); + return memory ? new (memory) xpath_ast_node(type, rettype, value) : 0; + } + + xpath_ast_node* alloc_node(ast_type_t type, xpath_value_type rettype, xpath_ast_node* left = 0, xpath_ast_node* right = 0) + { + void* memory = alloc_node(); + return memory ? new (memory) xpath_ast_node(type, rettype, left, right) : 0; + } + + xpath_ast_node* alloc_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents) + { + void* memory = alloc_node(); + return memory ? new (memory) xpath_ast_node(type, left, axis, test, contents) : 0; + } + + xpath_ast_node* alloc_node(ast_type_t type, xpath_ast_node* left, xpath_ast_node* right, predicate_t test) + { + void* memory = alloc_node(); + return memory ? new (memory) xpath_ast_node(type, left, right, test) : 0; + } + + const char_t* alloc_string(const xpath_lexer_string& value) + { + if (!value.begin) + return PUGIXML_TEXT(""); + + size_t length = static_cast(value.end - value.begin); + + char_t* c = static_cast(_alloc->allocate((length + 1) * sizeof(char_t))); + if (!c) return 0; + + memcpy(c, value.begin, length * sizeof(char_t)); + c[length] = 0; + + return c; + } + + xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2]) + { + switch (name.begin[0]) + { + case 'b': + if (name == PUGIXML_TEXT("boolean") && argc == 1) + return alloc_node(ast_func_boolean, xpath_type_boolean, args[0]); + + break; + + case 'c': + if (name == PUGIXML_TEXT("count") && argc == 1) + { + if (args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set"); + return alloc_node(ast_func_count, xpath_type_number, args[0]); + } + else if (name == PUGIXML_TEXT("contains") && argc == 2) + return alloc_node(ast_func_contains, xpath_type_boolean, args[0], args[1]); + else if (name == PUGIXML_TEXT("concat") && argc >= 2) + return alloc_node(ast_func_concat, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("ceiling") && argc == 1) + return alloc_node(ast_func_ceiling, xpath_type_number, args[0]); + + break; + + case 'f': + if (name == PUGIXML_TEXT("false") && argc == 0) + return alloc_node(ast_func_false, xpath_type_boolean); + else if (name == PUGIXML_TEXT("floor") && argc == 1) + return alloc_node(ast_func_floor, xpath_type_number, args[0]); + + break; + + case 'i': + if (name == PUGIXML_TEXT("id") && argc == 1) + return alloc_node(ast_func_id, xpath_type_node_set, args[0]); + + break; + + case 'l': + if (name == PUGIXML_TEXT("last") && argc == 0) + return alloc_node(ast_func_last, xpath_type_number); + else if (name == PUGIXML_TEXT("lang") && argc == 1) + return alloc_node(ast_func_lang, xpath_type_boolean, args[0]); + else if (name == PUGIXML_TEXT("local-name") && argc <= 1) + { + if (argc == 1 && args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set"); + return alloc_node(argc == 0 ? ast_func_local_name_0 : ast_func_local_name_1, xpath_type_string, args[0]); + } + + break; + + case 'n': + if (name == PUGIXML_TEXT("name") && argc <= 1) + { + if (argc == 1 && args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set"); + return alloc_node(argc == 0 ? ast_func_name_0 : ast_func_name_1, xpath_type_string, args[0]); + } + else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1) + { + if (argc == 1 && args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set"); + return alloc_node(argc == 0 ? ast_func_namespace_uri_0 : ast_func_namespace_uri_1, xpath_type_string, args[0]); + } + else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1) + return alloc_node(argc == 0 ? ast_func_normalize_space_0 : ast_func_normalize_space_1, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("not") && argc == 1) + return alloc_node(ast_func_not, xpath_type_boolean, args[0]); + else if (name == PUGIXML_TEXT("number") && argc <= 1) + return alloc_node(argc == 0 ? ast_func_number_0 : ast_func_number_1, xpath_type_number, args[0]); + + break; + + case 'p': + if (name == PUGIXML_TEXT("position") && argc == 0) + return alloc_node(ast_func_position, xpath_type_number); + + break; + + case 'r': + if (name == PUGIXML_TEXT("round") && argc == 1) + return alloc_node(ast_func_round, xpath_type_number, args[0]); + + break; + + case 's': + if (name == PUGIXML_TEXT("string") && argc <= 1) + return alloc_node(argc == 0 ? ast_func_string_0 : ast_func_string_1, xpath_type_string, args[0]); + else if (name == PUGIXML_TEXT("string-length") && argc <= 1) + return alloc_node(argc == 0 ? ast_func_string_length_0 : ast_func_string_length_1, xpath_type_number, args[0]); + else if (name == PUGIXML_TEXT("starts-with") && argc == 2) + return alloc_node(ast_func_starts_with, xpath_type_boolean, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring-before") && argc == 2) + return alloc_node(ast_func_substring_before, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring-after") && argc == 2) + return alloc_node(ast_func_substring_after, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3)) + return alloc_node(argc == 2 ? ast_func_substring_2 : ast_func_substring_3, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("sum") && argc == 1) + { + if (args[0]->rettype() != xpath_type_node_set) return error("Function has to be applied to node set"); + return alloc_node(ast_func_sum, xpath_type_number, args[0]); + } + + break; + + case 't': + if (name == PUGIXML_TEXT("translate") && argc == 3) + return alloc_node(ast_func_translate, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("true") && argc == 0) + return alloc_node(ast_func_true, xpath_type_boolean); + + break; + + default: + break; + } + + return error("Unrecognized function or wrong parameter count"); + } + + axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified) + { + specified = true; + + switch (name.begin[0]) + { + case 'a': + if (name == PUGIXML_TEXT("ancestor")) + return axis_ancestor; + else if (name == PUGIXML_TEXT("ancestor-or-self")) + return axis_ancestor_or_self; + else if (name == PUGIXML_TEXT("attribute")) + return axis_attribute; + + break; + + case 'c': + if (name == PUGIXML_TEXT("child")) + return axis_child; + + break; + + case 'd': + if (name == PUGIXML_TEXT("descendant")) + return axis_descendant; + else if (name == PUGIXML_TEXT("descendant-or-self")) + return axis_descendant_or_self; + + break; + + case 'f': + if (name == PUGIXML_TEXT("following")) + return axis_following; + else if (name == PUGIXML_TEXT("following-sibling")) + return axis_following_sibling; + + break; + + case 'n': + if (name == PUGIXML_TEXT("namespace")) + return axis_namespace; + + break; + + case 'p': + if (name == PUGIXML_TEXT("parent")) + return axis_parent; + else if (name == PUGIXML_TEXT("preceding")) + return axis_preceding; + else if (name == PUGIXML_TEXT("preceding-sibling")) + return axis_preceding_sibling; + + break; + + case 's': + if (name == PUGIXML_TEXT("self")) + return axis_self; + + break; + + default: + break; + } + + specified = false; + return axis_child; + } + + nodetest_t parse_node_test_type(const xpath_lexer_string& name) + { + switch (name.begin[0]) + { + case 'c': + if (name == PUGIXML_TEXT("comment")) + return nodetest_type_comment; + + break; + + case 'n': + if (name == PUGIXML_TEXT("node")) + return nodetest_type_node; + + break; + + case 'p': + if (name == PUGIXML_TEXT("processing-instruction")) + return nodetest_type_pi; + + break; + + case 't': + if (name == PUGIXML_TEXT("text")) + return nodetest_type_text; + + break; + + default: + break; + } + + return nodetest_none; + } + + // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall + xpath_ast_node* parse_primary_expression() + { + switch (_lexer.current()) + { + case lex_var_ref: + { + xpath_lexer_string name = _lexer.contents(); + + if (!_variables) + return error("Unknown variable: variable set is not provided"); + + xpath_variable* var = 0; + if (!get_variable_scratch(_scratch, _variables, name.begin, name.end, &var)) + return error_oom(); + + if (!var) + return error("Unknown variable: variable set does not contain the given name"); + + _lexer.next(); + + return alloc_node(ast_variable, var->type(), var); + } + + case lex_open_brace: + { + _lexer.next(); + + xpath_ast_node* n = parse_expression(); + if (!n) return 0; + + if (_lexer.current() != lex_close_brace) + return error("Expected ')' to match an opening '('"); + + _lexer.next(); + + return n; + } + + case lex_quoted_string: + { + const char_t* value = alloc_string(_lexer.contents()); + if (!value) return 0; + + _lexer.next(); + + return alloc_node(ast_string_constant, xpath_type_string, value); + } + + case lex_number: + { + double value = 0; + + if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value)) + return error_oom(); + + _lexer.next(); + + return alloc_node(ast_number_constant, xpath_type_number, value); + } + + case lex_string: + { + xpath_ast_node* args[2] = {0}; + size_t argc = 0; + + xpath_lexer_string function = _lexer.contents(); + _lexer.next(); + + xpath_ast_node* last_arg = 0; + + if (_lexer.current() != lex_open_brace) + return error("Unrecognized function call"); + _lexer.next(); + + size_t old_depth = _depth; + + while (_lexer.current() != lex_close_brace) + { + if (argc > 0) + { + if (_lexer.current() != lex_comma) + return error("No comma between function arguments"); + _lexer.next(); + } + + if (++_depth > xpath_ast_depth_limit) + return error_rec(); + + xpath_ast_node* n = parse_expression(); + if (!n) return 0; + + if (argc < 2) args[argc] = n; + else last_arg->set_next(n); + + argc++; + last_arg = n; + } + + _lexer.next(); + + _depth = old_depth; + + return parse_function(function, argc, args); + } + + default: + return error("Unrecognizable primary expression"); + } + } + + // FilterExpr ::= PrimaryExpr | FilterExpr Predicate + // Predicate ::= '[' PredicateExpr ']' + // PredicateExpr ::= Expr + xpath_ast_node* parse_filter_expression() + { + xpath_ast_node* n = parse_primary_expression(); + if (!n) return 0; + + size_t old_depth = _depth; + + while (_lexer.current() == lex_open_square_brace) + { + _lexer.next(); + + if (++_depth > xpath_ast_depth_limit) + return error_rec(); + + if (n->rettype() != xpath_type_node_set) + return error("Predicate has to be applied to node set"); + + xpath_ast_node* expr = parse_expression(); + if (!expr) return 0; + + n = alloc_node(ast_filter, n, expr, predicate_default); + if (!n) return 0; + + if (_lexer.current() != lex_close_square_brace) + return error("Expected ']' to match an opening '['"); + + _lexer.next(); + } + + _depth = old_depth; + + return n; + } + + // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep + // AxisSpecifier ::= AxisName '::' | '@'? + // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' + // NameTest ::= '*' | NCName ':' '*' | QName + // AbbreviatedStep ::= '.' | '..' + xpath_ast_node* parse_step(xpath_ast_node* set) + { + if (set && set->rettype() != xpath_type_node_set) + return error("Step has to be applied to node set"); + + bool axis_specified = false; + axis_t axis = axis_child; // implied child axis + + if (_lexer.current() == lex_axis_attribute) + { + axis = axis_attribute; + axis_specified = true; + + _lexer.next(); + } + else if (_lexer.current() == lex_dot) + { + _lexer.next(); + + if (_lexer.current() == lex_open_square_brace) + return error("Predicates are not allowed after an abbreviated step"); + + return alloc_node(ast_step, set, axis_self, nodetest_type_node, 0); + } + else if (_lexer.current() == lex_double_dot) + { + _lexer.next(); + + if (_lexer.current() == lex_open_square_brace) + return error("Predicates are not allowed after an abbreviated step"); + + return alloc_node(ast_step, set, axis_parent, nodetest_type_node, 0); + } + + nodetest_t nt_type = nodetest_none; + xpath_lexer_string nt_name; + + if (_lexer.current() == lex_string) + { + // node name test + nt_name = _lexer.contents(); + _lexer.next(); + + // was it an axis name? + if (_lexer.current() == lex_double_colon) + { + // parse axis name + if (axis_specified) + return error("Two axis specifiers in one step"); + + axis = parse_axis_name(nt_name, axis_specified); + + if (!axis_specified) + return error("Unknown axis"); + + // read actual node test + _lexer.next(); + + if (_lexer.current() == lex_multiply) + { + nt_type = nodetest_all; + nt_name = xpath_lexer_string(); + _lexer.next(); + } + else if (_lexer.current() == lex_string) + { + nt_name = _lexer.contents(); + _lexer.next(); + } + else + { + return error("Unrecognized node test"); + } + } + + if (nt_type == nodetest_none) + { + // node type test or processing-instruction + if (_lexer.current() == lex_open_brace) + { + _lexer.next(); + + if (_lexer.current() == lex_close_brace) + { + _lexer.next(); + + nt_type = parse_node_test_type(nt_name); + + if (nt_type == nodetest_none) + return error("Unrecognized node type"); + + nt_name = xpath_lexer_string(); + } + else if (nt_name == PUGIXML_TEXT("processing-instruction")) + { + if (_lexer.current() != lex_quoted_string) + return error("Only literals are allowed as arguments to processing-instruction()"); + + nt_type = nodetest_pi; + nt_name = _lexer.contents(); + _lexer.next(); + + if (_lexer.current() != lex_close_brace) + return error("Unmatched brace near processing-instruction()"); + _lexer.next(); + } + else + { + return error("Unmatched brace near node type test"); + } + } + // QName or NCName:* + else + { + if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:* + { + nt_name.end--; // erase * + + nt_type = nodetest_all_in_namespace; + } + else + { + nt_type = nodetest_name; + } + } + } + } + else if (_lexer.current() == lex_multiply) + { + nt_type = nodetest_all; + _lexer.next(); + } + else + { + return error("Unrecognized node test"); + } + + const char_t* nt_name_copy = alloc_string(nt_name); + if (!nt_name_copy) return 0; + + xpath_ast_node* n = alloc_node(ast_step, set, axis, nt_type, nt_name_copy); + if (!n) return 0; + + size_t old_depth = _depth; + + xpath_ast_node* last = 0; + + while (_lexer.current() == lex_open_square_brace) + { + _lexer.next(); + + if (++_depth > xpath_ast_depth_limit) + return error_rec(); + + xpath_ast_node* expr = parse_expression(); + if (!expr) return 0; + + xpath_ast_node* pred = alloc_node(ast_predicate, 0, expr, predicate_default); + if (!pred) return 0; + + if (_lexer.current() != lex_close_square_brace) + return error("Expected ']' to match an opening '['"); + _lexer.next(); + + if (last) last->set_next(pred); + else n->set_right(pred); + + last = pred; + } + + _depth = old_depth; + + return n; + } + + // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step + xpath_ast_node* parse_relative_location_path(xpath_ast_node* set) + { + xpath_ast_node* n = parse_step(set); + if (!n) return 0; + + size_t old_depth = _depth; + + while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) + { + lexeme_t l = _lexer.current(); + _lexer.next(); + + if (l == lex_double_slash) + { + n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + if (!n) return 0; + + ++_depth; + } + + if (++_depth > xpath_ast_depth_limit) + return error_rec(); + + n = parse_step(n); + if (!n) return 0; + } + + _depth = old_depth; + + return n; + } + + // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath + // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath + xpath_ast_node* parse_location_path() + { + if (_lexer.current() == lex_slash) + { + _lexer.next(); + + xpath_ast_node* n = alloc_node(ast_step_root, xpath_type_node_set); + if (!n) return 0; + + // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path + lexeme_t l = _lexer.current(); + + if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply) + return parse_relative_location_path(n); + else + return n; + } + else if (_lexer.current() == lex_double_slash) + { + _lexer.next(); + + xpath_ast_node* n = alloc_node(ast_step_root, xpath_type_node_set); + if (!n) return 0; + + n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + if (!n) return 0; + + return parse_relative_location_path(n); + } + + // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1 + return parse_relative_location_path(0); + } + + // PathExpr ::= LocationPath + // | FilterExpr + // | FilterExpr '/' RelativeLocationPath + // | FilterExpr '//' RelativeLocationPath + // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr + // UnaryExpr ::= UnionExpr | '-' UnaryExpr + xpath_ast_node* parse_path_or_unary_expression() + { + // Clarification. + // PathExpr begins with either LocationPath or FilterExpr. + // FilterExpr begins with PrimaryExpr + // PrimaryExpr begins with '$' in case of it being a variable reference, + // '(' in case of it being an expression, string literal, number constant or + // function call. + if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace || + _lexer.current() == lex_quoted_string || _lexer.current() == lex_number || + _lexer.current() == lex_string) + { + if (_lexer.current() == lex_string) + { + // This is either a function call, or not - if not, we shall proceed with location path + const char_t* state = _lexer.state(); + + while (PUGI__IS_CHARTYPE(*state, ct_space)) ++state; + + if (*state != '(') + return parse_location_path(); + + // This looks like a function call; however this still can be a node-test. Check it. + if (parse_node_test_type(_lexer.contents()) != nodetest_none) + return parse_location_path(); + } + + xpath_ast_node* n = parse_filter_expression(); + if (!n) return 0; + + if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) + { + lexeme_t l = _lexer.current(); + _lexer.next(); + + if (l == lex_double_slash) + { + if (n->rettype() != xpath_type_node_set) + return error("Step has to be applied to node set"); + + n = alloc_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + if (!n) return 0; + } + + // select from location path + return parse_relative_location_path(n); + } + + return n; + } + else if (_lexer.current() == lex_minus) + { + _lexer.next(); + + // precedence 7+ - only parses union expressions + xpath_ast_node* n = parse_expression(7); + if (!n) return 0; + + return alloc_node(ast_op_negate, xpath_type_number, n); + } + else + { + return parse_location_path(); + } + } + + struct binary_op_t + { + ast_type_t asttype; + xpath_value_type rettype; + int precedence; + + binary_op_t(): asttype(ast_unknown), rettype(xpath_type_none), precedence(0) + { + } + + binary_op_t(ast_type_t asttype_, xpath_value_type rettype_, int precedence_): asttype(asttype_), rettype(rettype_), precedence(precedence_) + { + } + + static binary_op_t parse(xpath_lexer& lexer) + { + switch (lexer.current()) + { + case lex_string: + if (lexer.contents() == PUGIXML_TEXT("or")) + return binary_op_t(ast_op_or, xpath_type_boolean, 1); + else if (lexer.contents() == PUGIXML_TEXT("and")) + return binary_op_t(ast_op_and, xpath_type_boolean, 2); + else if (lexer.contents() == PUGIXML_TEXT("div")) + return binary_op_t(ast_op_divide, xpath_type_number, 6); + else if (lexer.contents() == PUGIXML_TEXT("mod")) + return binary_op_t(ast_op_mod, xpath_type_number, 6); + else + return binary_op_t(); + + case lex_equal: + return binary_op_t(ast_op_equal, xpath_type_boolean, 3); + + case lex_not_equal: + return binary_op_t(ast_op_not_equal, xpath_type_boolean, 3); + + case lex_less: + return binary_op_t(ast_op_less, xpath_type_boolean, 4); + + case lex_greater: + return binary_op_t(ast_op_greater, xpath_type_boolean, 4); + + case lex_less_or_equal: + return binary_op_t(ast_op_less_or_equal, xpath_type_boolean, 4); + + case lex_greater_or_equal: + return binary_op_t(ast_op_greater_or_equal, xpath_type_boolean, 4); + + case lex_plus: + return binary_op_t(ast_op_add, xpath_type_number, 5); + + case lex_minus: + return binary_op_t(ast_op_subtract, xpath_type_number, 5); + + case lex_multiply: + return binary_op_t(ast_op_multiply, xpath_type_number, 6); + + case lex_union: + return binary_op_t(ast_op_union, xpath_type_node_set, 7); + + default: + return binary_op_t(); + } + } + }; + + xpath_ast_node* parse_expression_rec(xpath_ast_node* lhs, int limit) + { + binary_op_t op = binary_op_t::parse(_lexer); + + while (op.asttype != ast_unknown && op.precedence >= limit) + { + _lexer.next(); + + if (++_depth > xpath_ast_depth_limit) + return error_rec(); + + xpath_ast_node* rhs = parse_path_or_unary_expression(); + if (!rhs) return 0; + + binary_op_t nextop = binary_op_t::parse(_lexer); + + while (nextop.asttype != ast_unknown && nextop.precedence > op.precedence) + { + rhs = parse_expression_rec(rhs, nextop.precedence); + if (!rhs) return 0; + + nextop = binary_op_t::parse(_lexer); + } + + if (op.asttype == ast_op_union && (lhs->rettype() != xpath_type_node_set || rhs->rettype() != xpath_type_node_set)) + return error("Union operator has to be applied to node sets"); + + lhs = alloc_node(op.asttype, op.rettype, lhs, rhs); + if (!lhs) return 0; + + op = binary_op_t::parse(_lexer); + } + + return lhs; + } + + // Expr ::= OrExpr + // OrExpr ::= AndExpr | OrExpr 'or' AndExpr + // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr + // EqualityExpr ::= RelationalExpr + // | EqualityExpr '=' RelationalExpr + // | EqualityExpr '!=' RelationalExpr + // RelationalExpr ::= AdditiveExpr + // | RelationalExpr '<' AdditiveExpr + // | RelationalExpr '>' AdditiveExpr + // | RelationalExpr '<=' AdditiveExpr + // | RelationalExpr '>=' AdditiveExpr + // AdditiveExpr ::= MultiplicativeExpr + // | AdditiveExpr '+' MultiplicativeExpr + // | AdditiveExpr '-' MultiplicativeExpr + // MultiplicativeExpr ::= UnaryExpr + // | MultiplicativeExpr '*' UnaryExpr + // | MultiplicativeExpr 'div' UnaryExpr + // | MultiplicativeExpr 'mod' UnaryExpr + xpath_ast_node* parse_expression(int limit = 0) + { + size_t old_depth = _depth; + + if (++_depth > xpath_ast_depth_limit) + return error_rec(); + + xpath_ast_node* n = parse_path_or_unary_expression(); + if (!n) return 0; + + n = parse_expression_rec(n, limit); + + _depth = old_depth; + + return n; + } + + xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result): _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result), _depth(0) + { + } + + xpath_ast_node* parse() + { + xpath_ast_node* n = parse_expression(); + if (!n) return 0; + + assert(_depth == 0); + + // check if there are unparsed tokens left + if (_lexer.current() != lex_eof) + return error("Incorrect query"); + + return n; + } + + static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result) + { + xpath_parser parser(query, variables, alloc, result); + + return parser.parse(); + } + }; + + struct xpath_query_impl + { + static xpath_query_impl* create() + { + void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); + if (!memory) return 0; + + return new (memory) xpath_query_impl(); + } + + static void destroy(xpath_query_impl* impl) + { + // free all allocated pages + impl->alloc.release(); + + // free allocator memory (with the first page) + xml_memory::deallocate(impl); + } + + xpath_query_impl(): root(0), alloc(&block, &oom), oom(false) + { + block.next = 0; + block.capacity = sizeof(block.data); + } + + xpath_ast_node* root; + xpath_allocator alloc; + xpath_memory_block block; + bool oom; + }; + + PUGI__FN impl::xpath_ast_node* evaluate_node_set_prepare(xpath_query_impl* impl) + { + if (!impl) return 0; + + if (impl->root->rettype() != xpath_type_node_set) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return 0; + #else + xpath_parse_result res; + res.error = "Expression does not evaluate to node set"; + + throw xpath_exception(res); + #endif + } + + return impl->root; + } +PUGI__NS_END + +namespace pugi +{ +#ifndef PUGIXML_NO_EXCEPTIONS + PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) + { + assert(_result.error); + } + + PUGI__FN const char* xpath_exception::what() const throw() + { + return _result.error; + } + + PUGI__FN const xpath_parse_result& xpath_exception::result() const + { + return _result; + } +#endif + + PUGI__FN xpath_node::xpath_node() + { + } + + PUGI__FN xpath_node::xpath_node(const xml_node& node_): _node(node_) + { + } + + PUGI__FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) + { + } + + PUGI__FN xml_node xpath_node::node() const + { + return _attribute ? xml_node() : _node; + } + + PUGI__FN xml_attribute xpath_node::attribute() const + { + return _attribute; + } + + PUGI__FN xml_node xpath_node::parent() const + { + return _attribute ? _node : _node.parent(); + } + + PUGI__FN static void unspecified_bool_xpath_node(xpath_node***) + { + } + + PUGI__FN xpath_node::operator xpath_node::unspecified_bool_type() const + { + return (_node || _attribute) ? unspecified_bool_xpath_node : 0; + } + + PUGI__FN bool xpath_node::operator!() const + { + return !(_node || _attribute); + } + + PUGI__FN bool xpath_node::operator==(const xpath_node& n) const + { + return _node == n._node && _attribute == n._attribute; + } + + PUGI__FN bool xpath_node::operator!=(const xpath_node& n) const + { + return _node != n._node || _attribute != n._attribute; + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xpath_node& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xpath_node& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_, type_t type_) + { + assert(begin_ <= end_); + + size_t size_ = static_cast(end_ - begin_); + + // use internal buffer for 0 or 1 elements, heap buffer otherwise + xpath_node* storage = (size_ <= 1) ? _storage : static_cast(impl::xml_memory::allocate(size_ * sizeof(xpath_node))); + + if (!storage) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return; + #else + throw std::bad_alloc(); + #endif + } + + // deallocate old buffer + if (_begin != _storage) + impl::xml_memory::deallocate(_begin); + + // size check is necessary because for begin_ = end_ = nullptr, memcpy is UB + if (size_) + memcpy(storage, begin_, size_ * sizeof(xpath_node)); + + _begin = storage; + _end = storage + size_; + _type = type_; + } + +#ifdef PUGIXML_HAS_MOVE + PUGI__FN void xpath_node_set::_move(xpath_node_set& rhs) PUGIXML_NOEXCEPT + { + _type = rhs._type; + _storage[0] = rhs._storage[0]; + _begin = (rhs._begin == rhs._storage) ? _storage : rhs._begin; + _end = _begin + (rhs._end - rhs._begin); + + rhs._type = type_unsorted; + rhs._begin = rhs._storage; + rhs._end = rhs._storage; + } +#endif + + PUGI__FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(_storage), _end(_storage) + { + } + + PUGI__FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_unsorted), _begin(_storage), _end(_storage) + { + _assign(begin_, end_, type_); + } + + PUGI__FN xpath_node_set::~xpath_node_set() + { + if (_begin != _storage) + impl::xml_memory::deallocate(_begin); + } + + PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(type_unsorted), _begin(_storage), _end(_storage) + { + _assign(ns._begin, ns._end, ns._type); + } + + PUGI__FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) + { + if (this == &ns) return *this; + + _assign(ns._begin, ns._end, ns._type); + + return *this; + } + +#ifdef PUGIXML_HAS_MOVE + PUGI__FN xpath_node_set::xpath_node_set(xpath_node_set&& rhs) PUGIXML_NOEXCEPT: _type(type_unsorted), _begin(_storage), _end(_storage) + { + _move(rhs); + } + + PUGI__FN xpath_node_set& xpath_node_set::operator=(xpath_node_set&& rhs) PUGIXML_NOEXCEPT + { + if (this == &rhs) return *this; + + if (_begin != _storage) + impl::xml_memory::deallocate(_begin); + + _move(rhs); + + return *this; + } +#endif + + PUGI__FN xpath_node_set::type_t xpath_node_set::type() const + { + return _type; + } + + PUGI__FN size_t xpath_node_set::size() const + { + return _end - _begin; + } + + PUGI__FN bool xpath_node_set::empty() const + { + return _begin == _end; + } + + PUGI__FN const xpath_node& xpath_node_set::operator[](size_t index) const + { + assert(index < size()); + return _begin[index]; + } + + PUGI__FN xpath_node_set::const_iterator xpath_node_set::begin() const + { + return _begin; + } + + PUGI__FN xpath_node_set::const_iterator xpath_node_set::end() const + { + return _end; + } + + PUGI__FN void xpath_node_set::sort(bool reverse) + { + _type = impl::xpath_sort(_begin, _end, _type, reverse); + } + + PUGI__FN xpath_node xpath_node_set::first() const + { + return impl::xpath_first(_begin, _end, _type); + } + + PUGI__FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0) + { + } + + PUGI__FN xpath_parse_result::operator bool() const + { + return error == 0; + } + + PUGI__FN const char* xpath_parse_result::description() const + { + return error ? error : "No error"; + } + + PUGI__FN xpath_variable::xpath_variable(xpath_value_type type_): _type(type_), _next(0) + { + } + + PUGI__FN const char_t* xpath_variable::name() const + { + switch (_type) + { + case xpath_type_node_set: + return static_cast(this)->name; + + case xpath_type_number: + return static_cast(this)->name; + + case xpath_type_string: + return static_cast(this)->name; + + case xpath_type_boolean: + return static_cast(this)->name; + + default: + assert(false && "Invalid variable type"); // unreachable + return 0; + } + } + + PUGI__FN xpath_value_type xpath_variable::type() const + { + return _type; + } + + PUGI__FN bool xpath_variable::get_boolean() const + { + return (_type == xpath_type_boolean) ? static_cast(this)->value : false; + } + + PUGI__FN double xpath_variable::get_number() const + { + return (_type == xpath_type_number) ? static_cast(this)->value : impl::gen_nan(); + } + + PUGI__FN const char_t* xpath_variable::get_string() const + { + const char_t* value = (_type == xpath_type_string) ? static_cast(this)->value : 0; + return value ? value : PUGIXML_TEXT(""); + } + + PUGI__FN const xpath_node_set& xpath_variable::get_node_set() const + { + return (_type == xpath_type_node_set) ? static_cast(this)->value : impl::dummy_node_set; + } + + PUGI__FN bool xpath_variable::set(bool value) + { + if (_type != xpath_type_boolean) return false; + + static_cast(this)->value = value; + return true; + } + + PUGI__FN bool xpath_variable::set(double value) + { + if (_type != xpath_type_number) return false; + + static_cast(this)->value = value; + return true; + } + + PUGI__FN bool xpath_variable::set(const char_t* value) + { + if (_type != xpath_type_string) return false; + + impl::xpath_variable_string* var = static_cast(this); + + // duplicate string + size_t size = (impl::strlength(value) + 1) * sizeof(char_t); + + char_t* copy = static_cast(impl::xml_memory::allocate(size)); + if (!copy) return false; + + memcpy(copy, value, size); + + // replace old string + if (var->value) impl::xml_memory::deallocate(var->value); + var->value = copy; + + return true; + } + + PUGI__FN bool xpath_variable::set(const xpath_node_set& value) + { + if (_type != xpath_type_node_set) return false; + + static_cast(this)->value = value; + return true; + } + + PUGI__FN xpath_variable_set::xpath_variable_set() + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + _data[i] = 0; + } + + PUGI__FN xpath_variable_set::~xpath_variable_set() + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + _destroy(_data[i]); + } + + PUGI__FN xpath_variable_set::xpath_variable_set(const xpath_variable_set& rhs) + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + _data[i] = 0; + + _assign(rhs); + } + + PUGI__FN xpath_variable_set& xpath_variable_set::operator=(const xpath_variable_set& rhs) + { + if (this == &rhs) return *this; + + _assign(rhs); + + return *this; + } + +#ifdef PUGIXML_HAS_MOVE + PUGI__FN xpath_variable_set::xpath_variable_set(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + { + _data[i] = rhs._data[i]; + rhs._data[i] = 0; + } + } + + PUGI__FN xpath_variable_set& xpath_variable_set::operator=(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + { + _destroy(_data[i]); + + _data[i] = rhs._data[i]; + rhs._data[i] = 0; + } + + return *this; + } +#endif + + PUGI__FN void xpath_variable_set::_assign(const xpath_variable_set& rhs) + { + xpath_variable_set temp; + + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + if (rhs._data[i] && !_clone(rhs._data[i], &temp._data[i])) + return; + + _swap(temp); + } + + PUGI__FN void xpath_variable_set::_swap(xpath_variable_set& rhs) + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + { + xpath_variable* chain = _data[i]; + + _data[i] = rhs._data[i]; + rhs._data[i] = chain; + } + } + + PUGI__FN xpath_variable* xpath_variable_set::_find(const char_t* name) const + { + const size_t hash_size = sizeof(_data) / sizeof(_data[0]); + size_t hash = impl::hash_string(name) % hash_size; + + // look for existing variable + for (xpath_variable* var = _data[hash]; var; var = var->_next) + if (impl::strequal(var->name(), name)) + return var; + + return 0; + } + + PUGI__FN bool xpath_variable_set::_clone(xpath_variable* var, xpath_variable** out_result) + { + xpath_variable* last = 0; + + while (var) + { + // allocate storage for new variable + xpath_variable* nvar = impl::new_xpath_variable(var->_type, var->name()); + if (!nvar) return false; + + // link the variable to the result immediately to handle failures gracefully + if (last) + last->_next = nvar; + else + *out_result = nvar; + + last = nvar; + + // copy the value; this can fail due to out-of-memory conditions + if (!impl::copy_xpath_variable(nvar, var)) return false; + + var = var->_next; + } + + return true; + } + + PUGI__FN void xpath_variable_set::_destroy(xpath_variable* var) + { + while (var) + { + xpath_variable* next = var->_next; + + impl::delete_xpath_variable(var->_type, var); + + var = next; + } + } + + PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) + { + const size_t hash_size = sizeof(_data) / sizeof(_data[0]); + size_t hash = impl::hash_string(name) % hash_size; + + // look for existing variable + for (xpath_variable* var = _data[hash]; var; var = var->_next) + if (impl::strequal(var->name(), name)) + return var->type() == type ? var : 0; + + // add new variable + xpath_variable* result = impl::new_xpath_variable(type, name); + + if (result) + { + result->_next = _data[hash]; + + _data[hash] = result; + } + + return result; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, bool value) + { + xpath_variable* var = add(name, xpath_type_boolean); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, double value) + { + xpath_variable* var = add(name, xpath_type_number); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, const char_t* value) + { + xpath_variable* var = add(name, xpath_type_string); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) + { + xpath_variable* var = add(name, xpath_type_node_set); + return var ? var->set(value) : false; + } + + PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name) + { + return _find(name); + } + + PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const + { + return _find(name); + } + + PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0) + { + impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create(); + + if (!qimpl) + { + #ifdef PUGIXML_NO_EXCEPTIONS + _result.error = "Out of memory"; + #else + throw std::bad_alloc(); + #endif + } + else + { + using impl::auto_deleter; // MSVC7 workaround + auto_deleter impl(qimpl, impl::xpath_query_impl::destroy); + + qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result); + + if (qimpl->root) + { + qimpl->root->optimize(&qimpl->alloc); + + _impl = impl.release(); + _result.error = 0; + } + else + { + #ifdef PUGIXML_NO_EXCEPTIONS + if (qimpl->oom) _result.error = "Out of memory"; + #else + if (qimpl->oom) throw std::bad_alloc(); + throw xpath_exception(_result); + #endif + } + } + } + + PUGI__FN xpath_query::xpath_query(): _impl(0) + { + } + + PUGI__FN xpath_query::~xpath_query() + { + if (_impl) + impl::xpath_query_impl::destroy(static_cast(_impl)); + } + +#ifdef PUGIXML_HAS_MOVE + PUGI__FN xpath_query::xpath_query(xpath_query&& rhs) PUGIXML_NOEXCEPT + { + _impl = rhs._impl; + _result = rhs._result; + rhs._impl = 0; + rhs._result = xpath_parse_result(); + } + + PUGI__FN xpath_query& xpath_query::operator=(xpath_query&& rhs) PUGIXML_NOEXCEPT + { + if (this == &rhs) return *this; + + if (_impl) + impl::xpath_query_impl::destroy(static_cast(_impl)); + + _impl = rhs._impl; + _result = rhs._result; + rhs._impl = 0; + rhs._result = xpath_parse_result(); + + return *this; + } +#endif + + PUGI__FN xpath_value_type xpath_query::return_type() const + { + if (!_impl) return xpath_type_none; + + return static_cast(_impl)->root->rettype(); + } + + PUGI__FN bool xpath_query::evaluate_boolean(const xpath_node& n) const + { + if (!_impl) return false; + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + bool r = static_cast(_impl)->root->eval_boolean(c, sd.stack); + + if (sd.oom) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return false; + #else + throw std::bad_alloc(); + #endif + } + + return r; + } + + PUGI__FN double xpath_query::evaluate_number(const xpath_node& n) const + { + if (!_impl) return impl::gen_nan(); + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + double r = static_cast(_impl)->root->eval_number(c, sd.stack); + + if (sd.oom) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return impl::gen_nan(); + #else + throw std::bad_alloc(); + #endif + } + + return r; + } + +#ifndef PUGIXML_NO_STL + PUGI__FN string_t xpath_query::evaluate_string(const xpath_node& n) const + { + if (!_impl) return string_t(); + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + impl::xpath_string r = static_cast(_impl)->root->eval_string(c, sd.stack); + + if (sd.oom) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return string_t(); + #else + throw std::bad_alloc(); + #endif + } + + return string_t(r.c_str(), r.length()); + } +#endif + + PUGI__FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const + { + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + impl::xpath_string r = _impl ? static_cast(_impl)->root->eval_string(c, sd.stack) : impl::xpath_string(); + + if (sd.oom) + { + #ifdef PUGIXML_NO_EXCEPTIONS + r = impl::xpath_string(); + #else + throw std::bad_alloc(); + #endif + } + + size_t full_size = r.length() + 1; + + if (capacity > 0) + { + size_t size = (full_size < capacity) ? full_size : capacity; + assert(size > 0); + + memcpy(buffer, r.c_str(), (size - 1) * sizeof(char_t)); + buffer[size - 1] = 0; + } + + return full_size; + } + + PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const + { + impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast(_impl)); + if (!root) return xpath_node_set(); + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_all); + + if (sd.oom) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return xpath_node_set(); + #else + throw std::bad_alloc(); + #endif + } + + return xpath_node_set(r.begin(), r.end(), r.type()); + } + + PUGI__FN xpath_node xpath_query::evaluate_node(const xpath_node& n) const + { + impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast(_impl)); + if (!root) return xpath_node(); + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_first); + + if (sd.oom) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return xpath_node(); + #else + throw std::bad_alloc(); + #endif + } + + return r.first(); + } + + PUGI__FN const xpath_parse_result& xpath_query::result() const + { + return _result; + } + + PUGI__FN static void unspecified_bool_xpath_query(xpath_query***) + { + } + + PUGI__FN xpath_query::operator xpath_query::unspecified_bool_type() const + { + return _impl ? unspecified_bool_xpath_query : 0; + } + + PUGI__FN bool xpath_query::operator!() const + { + return !_impl; + } + + PUGI__FN xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables) const + { + xpath_query q(query, variables); + return q.evaluate_node(*this); + } + + PUGI__FN xpath_node xml_node::select_node(const xpath_query& query) const + { + return query.evaluate_node(*this); + } + + PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const + { + xpath_query q(query, variables); + return q.evaluate_node_set(*this); + } + + PUGI__FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const + { + return query.evaluate_node_set(*this); + } + + PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const + { + xpath_query q(query, variables); + return q.evaluate_node(*this); + } + + PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const + { + return query.evaluate_node(*this); + } +} + +#endif + +#ifdef __BORLANDC__ +# pragma option pop +#endif + +// Intel C++ does not properly keep warning state for function templates, +// so popping warning state at the end of translation unit leads to warnings in the middle. +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) +# pragma warning(pop) +#endif + +#if defined(_MSC_VER) && defined(__c2__) +# pragma clang diagnostic pop +#endif + +// Undefine all local macros (makes sure we're not leaking macros in header-only mode) +#undef PUGI__NO_INLINE +#undef PUGI__UNLIKELY +#undef PUGI__STATIC_ASSERT +#undef PUGI__DMC_VOLATILE +#undef PUGI__UNSIGNED_OVERFLOW +#undef PUGI__MSVC_CRT_VERSION +#undef PUGI__SNPRINTF +#undef PUGI__NS_BEGIN +#undef PUGI__NS_END +#undef PUGI__FN +#undef PUGI__FN_NO_INLINE +#undef PUGI__GETHEADER_IMPL +#undef PUGI__GETPAGE_IMPL +#undef PUGI__GETPAGE +#undef PUGI__NODETYPE +#undef PUGI__IS_CHARTYPE_IMPL +#undef PUGI__IS_CHARTYPE +#undef PUGI__IS_CHARTYPEX +#undef PUGI__ENDSWITH +#undef PUGI__SKIPWS +#undef PUGI__OPTSET +#undef PUGI__PUSHNODE +#undef PUGI__POPNODE +#undef PUGI__SCANFOR +#undef PUGI__SCANWHILE +#undef PUGI__SCANWHILE_UNROLL +#undef PUGI__ENDSEG +#undef PUGI__THROW_ERROR +#undef PUGI__CHECK_ERROR + +#endif + +/** + * Copyright (c) 2006-2022 Arseny Kapoulkine + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ diff --git a/MiaoUI/src/source/ThirdParty/pugixml/pugixml.hpp b/MiaoUI/src/source/ThirdParty/pugixml/pugixml.hpp new file mode 100644 index 0000000..5a8496d --- /dev/null +++ b/MiaoUI/src/source/ThirdParty/pugixml/pugixml.hpp @@ -0,0 +1,1501 @@ +/** + * pugixml parser - version 1.12 + * -------------------------------------------------------- + * Copyright (C) 2006-2022, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at https://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef PUGIXML_VERSION +// Define version macro; evaluates to major * 1000 + minor * 10 + patch so that it's safe to use in less-than comparisons +// Note: pugixml used major * 100 + minor * 10 + patch format up until 1.9 (which had version identifier 190); starting from pugixml 1.10, the minor version number is two digits +# define PUGIXML_VERSION 1110 +#endif + +// Include user configuration file (this can define various configuration macros) +#include "pugiconfig.hpp" + +#ifndef HEADER_PUGIXML_HPP +#define HEADER_PUGIXML_HPP + +// Include stddef.h for size_t and ptrdiff_t +#include + +// Include exception header for XPath +#if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS) +# include +#endif + +// Include STL headers +#ifndef PUGIXML_NO_STL +# include +# include +# include +#endif + +// Macro for deprecated features +#ifndef PUGIXML_DEPRECATED +# if defined(__GNUC__) +# define PUGIXML_DEPRECATED __attribute__((deprecated)) +# elif defined(_MSC_VER) && _MSC_VER >= 1300 +# define PUGIXML_DEPRECATED __declspec(deprecated) +# else +# define PUGIXML_DEPRECATED +# endif +#endif + +// If no API is defined, assume default +#ifndef PUGIXML_API +# define PUGIXML_API +#endif + +// If no API for classes is defined, assume default +#ifndef PUGIXML_CLASS +# define PUGIXML_CLASS PUGIXML_API +#endif + +// If no API for functions is defined, assume default +#ifndef PUGIXML_FUNCTION +# define PUGIXML_FUNCTION PUGIXML_API +#endif + +// If the platform is known to have long long support, enable long long functions +#ifndef PUGIXML_HAS_LONG_LONG +# if __cplusplus >= 201103 +# define PUGIXML_HAS_LONG_LONG +# elif defined(_MSC_VER) && _MSC_VER >= 1400 +# define PUGIXML_HAS_LONG_LONG +# endif +#endif + +// If the platform is known to have move semantics support, compile move ctor/operator implementation +#ifndef PUGIXML_HAS_MOVE +# if __cplusplus >= 201103 +# define PUGIXML_HAS_MOVE +# elif defined(_MSC_VER) && _MSC_VER >= 1600 +# define PUGIXML_HAS_MOVE +# endif +#endif + +// If C++ is 2011 or higher, add 'noexcept' specifiers +#ifndef PUGIXML_NOEXCEPT +# if __cplusplus >= 201103 +# define PUGIXML_NOEXCEPT noexcept +# elif defined(_MSC_VER) && _MSC_VER >= 1900 +# define PUGIXML_NOEXCEPT noexcept +# else +# define PUGIXML_NOEXCEPT +# endif +#endif + +// Some functions can not be noexcept in compact mode +#ifdef PUGIXML_COMPACT +# define PUGIXML_NOEXCEPT_IF_NOT_COMPACT +#else +# define PUGIXML_NOEXCEPT_IF_NOT_COMPACT PUGIXML_NOEXCEPT +#endif + +// If C++ is 2011 or higher, add 'override' qualifiers +#ifndef PUGIXML_OVERRIDE +# if __cplusplus >= 201103 +# define PUGIXML_OVERRIDE override +# elif defined(_MSC_VER) && _MSC_VER >= 1700 +# define PUGIXML_OVERRIDE override +# else +# define PUGIXML_OVERRIDE +# endif +#endif + +// If C++ is 2011 or higher, use 'nullptr' +#ifndef PUGIXML_NULL +# if __cplusplus >= 201103 +# define PUGIXML_NULL nullptr +# else +# define PUGIXML_NULL 0 +# endif +#endif + +// Character interface macros +#ifdef PUGIXML_WCHAR_MODE +# define PUGIXML_TEXT(t) L ## t +# define PUGIXML_CHAR wchar_t +#else +# define PUGIXML_TEXT(t) t +# define PUGIXML_CHAR char +#endif + +namespace pugi +{ + // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE + typedef PUGIXML_CHAR char_t; + +#ifndef PUGIXML_NO_STL + // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE + typedef std::basic_string, std::allocator > string_t; +#endif +} + +// The PugiXML namespace +namespace pugi +{ + // Tree node types + enum xml_node_type + { + node_null, // Empty (null) node handle + node_document, // A document tree's absolute root + node_element, // Element tag, i.e. '' + node_pcdata, // Plain character data, i.e. 'text' + node_cdata, // Character data, i.e. '' + node_comment, // Comment tag, i.e. '' + node_pi, // Processing instruction, i.e. '' + node_declaration, // Document declaration, i.e. '' + node_doctype // Document type declaration, i.e. '' + }; + + // Parsing options + + // Minimal parsing mode (equivalent to turning all other flags off). + // Only elements and PCDATA sections are added to the DOM tree, no text conversions are performed. + const unsigned int parse_minimal = 0x0000; + + // This flag determines if processing instructions (node_pi) are added to the DOM tree. This flag is off by default. + const unsigned int parse_pi = 0x0001; + + // This flag determines if comments (node_comment) are added to the DOM tree. This flag is off by default. + const unsigned int parse_comments = 0x0002; + + // This flag determines if CDATA sections (node_cdata) are added to the DOM tree. This flag is on by default. + const unsigned int parse_cdata = 0x0004; + + // This flag determines if plain character data (node_pcdata) that consist only of whitespace are added to the DOM tree. + // This flag is off by default; turning it on usually results in slower parsing and more memory consumption. + const unsigned int parse_ws_pcdata = 0x0008; + + // This flag determines if character and entity references are expanded during parsing. This flag is on by default. + const unsigned int parse_escapes = 0x0010; + + // This flag determines if EOL characters are normalized (converted to #xA) during parsing. This flag is on by default. + const unsigned int parse_eol = 0x0020; + + // This flag determines if attribute values are normalized using CDATA normalization rules during parsing. This flag is on by default. + const unsigned int parse_wconv_attribute = 0x0040; + + // This flag determines if attribute values are normalized using NMTOKENS normalization rules during parsing. This flag is off by default. + const unsigned int parse_wnorm_attribute = 0x0080; + + // This flag determines if document declaration (node_declaration) is added to the DOM tree. This flag is off by default. + const unsigned int parse_declaration = 0x0100; + + // This flag determines if document type declaration (node_doctype) is added to the DOM tree. This flag is off by default. + const unsigned int parse_doctype = 0x0200; + + // This flag determines if plain character data (node_pcdata) that is the only child of the parent node and that consists only + // of whitespace is added to the DOM tree. + // This flag is off by default; turning it on may result in slower parsing and more memory consumption. + const unsigned int parse_ws_pcdata_single = 0x0400; + + // This flag determines if leading and trailing whitespace is to be removed from plain character data. This flag is off by default. + const unsigned int parse_trim_pcdata = 0x0800; + + // This flag determines if plain character data that does not have a parent node is added to the DOM tree, and if an empty document + // is a valid document. This flag is off by default. + const unsigned int parse_fragment = 0x1000; + + // This flag determines if plain character data is be stored in the parent element's value. This significantly changes the structure of + // the document; this flag is only recommended for parsing documents with many PCDATA nodes in memory-constrained environments. + // This flag is off by default. + const unsigned int parse_embed_pcdata = 0x2000; + + // The default parsing mode. + // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded, + // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. + const unsigned int parse_default = parse_cdata | parse_escapes | parse_wconv_attribute | parse_eol; + + // The full parsing mode. + // Nodes of all types are added to the DOM tree, character/reference entities are expanded, + // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. + const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype; + + // These flags determine the encoding of input data for XML document + enum xml_encoding + { + encoding_auto, // Auto-detect input encoding using BOM or < / class xml_object_range + { + public: + typedef It const_iterator; + typedef It iterator; + + xml_object_range(It b, It e): _begin(b), _end(e) + { + } + + It begin() const { return _begin; } + It end() const { return _end; } + + bool empty() const { return _begin == _end; } + + private: + It _begin, _end; + }; + + // Writer interface for node printing (see xml_node::print) + class PUGIXML_CLASS xml_writer + { + public: + virtual ~xml_writer() {} + + // Write memory chunk into stream/file/whatever + virtual void write(const void* data, size_t size) = 0; + }; + + // xml_writer implementation for FILE* + class PUGIXML_CLASS xml_writer_file: public xml_writer + { + public: + // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio + xml_writer_file(void* file); + + virtual void write(const void* data, size_t size) PUGIXML_OVERRIDE; + + private: + void* file; + }; + + #ifndef PUGIXML_NO_STL + // xml_writer implementation for streams + class PUGIXML_CLASS xml_writer_stream: public xml_writer + { + public: + // Construct writer from an output stream object + xml_writer_stream(std::basic_ostream >& stream); + xml_writer_stream(std::basic_ostream >& stream); + + virtual void write(const void* data, size_t size) PUGIXML_OVERRIDE; + + private: + std::basic_ostream >* narrow_stream; + std::basic_ostream >* wide_stream; + }; + #endif + + // A light-weight handle for manipulating attributes in DOM tree + class PUGIXML_CLASS xml_attribute + { + friend class xml_attribute_iterator; + friend class xml_node; + + private: + xml_attribute_struct* _attr; + + typedef void (*unspecified_bool_type)(xml_attribute***); + + public: + // Default constructor. Constructs an empty attribute. + xml_attribute(); + + // Constructs attribute from internal pointer + explicit xml_attribute(xml_attribute_struct* attr); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators (compares wrapped attribute pointers) + bool operator==(const xml_attribute& r) const; + bool operator!=(const xml_attribute& r) const; + bool operator<(const xml_attribute& r) const; + bool operator>(const xml_attribute& r) const; + bool operator<=(const xml_attribute& r) const; + bool operator>=(const xml_attribute& r) const; + + // Check if attribute is empty + bool empty() const; + + // Get attribute name/value, or "" if attribute is empty + const char_t* name() const; + const char_t* value() const; + + // Get attribute value, or the default value if attribute is empty + const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; + + // Get attribute value as a number, or the default value if conversion did not succeed or attribute is empty + int as_int(int def = 0) const; + unsigned int as_uint(unsigned int def = 0) const; + double as_double(double def = 0) const; + float as_float(float def = 0) const; + + #ifdef PUGIXML_HAS_LONG_LONG + long long as_llong(long long def = 0) const; + unsigned long long as_ullong(unsigned long long def = 0) const; + #endif + + // Get attribute value as bool (returns true if first character is in '1tTyY' set), or the default value if attribute is empty + bool as_bool(bool def = false) const; + + // Set attribute name/value (returns false if attribute is empty or there is not enough memory) + bool set_name(const char_t* rhs); + bool set_value(const char_t* rhs); + + // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") + bool set_value(int rhs); + bool set_value(unsigned int rhs); + bool set_value(long rhs); + bool set_value(unsigned long rhs); + bool set_value(double rhs); + bool set_value(double rhs, int precision); + bool set_value(float rhs); + bool set_value(float rhs, int precision); + bool set_value(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + bool set_value(long long rhs); + bool set_value(unsigned long long rhs); + #endif + + // Set attribute value (equivalent to set_value without error checking) + xml_attribute& operator=(const char_t* rhs); + xml_attribute& operator=(int rhs); + xml_attribute& operator=(unsigned int rhs); + xml_attribute& operator=(long rhs); + xml_attribute& operator=(unsigned long rhs); + xml_attribute& operator=(double rhs); + xml_attribute& operator=(float rhs); + xml_attribute& operator=(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + xml_attribute& operator=(long long rhs); + xml_attribute& operator=(unsigned long long rhs); + #endif + + // Get next/previous attribute in the attribute list of the parent node + xml_attribute next_attribute() const; + xml_attribute previous_attribute() const; + + // Get hash value (unique for handles to the same object) + size_t hash_value() const; + + // Get internal pointer + xml_attribute_struct* internal_object() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs); +#endif + + // A light-weight handle for manipulating nodes in DOM tree + class PUGIXML_CLASS xml_node + { + friend class xml_attribute_iterator; + friend class xml_node_iterator; + friend class xml_named_node_iterator; + + protected: + xml_node_struct* _root; + + typedef void (*unspecified_bool_type)(xml_node***); + + public: + // Default constructor. Constructs an empty node. + xml_node(); + + // Constructs node from internal pointer + explicit xml_node(xml_node_struct* p); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators (compares wrapped node pointers) + bool operator==(const xml_node& r) const; + bool operator!=(const xml_node& r) const; + bool operator<(const xml_node& r) const; + bool operator>(const xml_node& r) const; + bool operator<=(const xml_node& r) const; + bool operator>=(const xml_node& r) const; + + // Check if node is empty. + bool empty() const; + + // Get node type + xml_node_type type() const; + + // Get node name, or "" if node is empty or it has no name + const char_t* name() const; + + // Get node value, or "" if node is empty or it has no value + // Note: For text node.value() does not return "text"! Use child_value() or text() methods to access text inside nodes. + const char_t* value() const; + + // Get attribute list + xml_attribute first_attribute() const; + xml_attribute last_attribute() const; + + // Get children list + xml_node first_child() const; + xml_node last_child() const; + + // Get next/previous sibling in the children list of the parent node + xml_node next_sibling() const; + xml_node previous_sibling() const; + + // Get parent node + xml_node parent() const; + + // Get root of DOM tree this node belongs to + xml_node root() const; + + // Get text object for the current node + xml_text text() const; + + // Get child, attribute or next/previous sibling with the specified name + xml_node child(const char_t* name) const; + xml_attribute attribute(const char_t* name) const; + xml_node next_sibling(const char_t* name) const; + xml_node previous_sibling(const char_t* name) const; + + // Get attribute, starting the search from a hint (and updating hint so that searching for a sequence of attributes is fast) + xml_attribute attribute(const char_t* name, xml_attribute& hint) const; + + // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA + const char_t* child_value() const; + + // Get child value of child with specified name. Equivalent to child(name).child_value(). + const char_t* child_value(const char_t* name) const; + + // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) + bool set_name(const char_t* rhs); + bool set_value(const char_t* rhs); + + // Add attribute with specified name. Returns added attribute, or empty attribute on errors. + xml_attribute append_attribute(const char_t* name); + xml_attribute prepend_attribute(const char_t* name); + xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr); + xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr); + + // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. + xml_attribute append_copy(const xml_attribute& proto); + xml_attribute prepend_copy(const xml_attribute& proto); + xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr); + xml_attribute insert_copy_before(const xml_attribute& proto, const xml_attribute& attr); + + // Add child node with specified type. Returns added node, or empty node on errors. + xml_node append_child(xml_node_type type = node_element); + xml_node prepend_child(xml_node_type type = node_element); + xml_node insert_child_after(xml_node_type type, const xml_node& node); + xml_node insert_child_before(xml_node_type type, const xml_node& node); + + // Add child element with specified name. Returns added node, or empty node on errors. + xml_node append_child(const char_t* name); + xml_node prepend_child(const char_t* name); + xml_node insert_child_after(const char_t* name, const xml_node& node); + xml_node insert_child_before(const char_t* name, const xml_node& node); + + // Add a copy of the specified node as a child. Returns added node, or empty node on errors. + xml_node append_copy(const xml_node& proto); + xml_node prepend_copy(const xml_node& proto); + xml_node insert_copy_after(const xml_node& proto, const xml_node& node); + xml_node insert_copy_before(const xml_node& proto, const xml_node& node); + + // Move the specified node to become a child of this node. Returns moved node, or empty node on errors. + xml_node append_move(const xml_node& moved); + xml_node prepend_move(const xml_node& moved); + xml_node insert_move_after(const xml_node& moved, const xml_node& node); + xml_node insert_move_before(const xml_node& moved, const xml_node& node); + + // Remove specified attribute + bool remove_attribute(const xml_attribute& a); + bool remove_attribute(const char_t* name); + + // Remove all attributes + bool remove_attributes(); + + // Remove specified child + bool remove_child(const xml_node& n); + bool remove_child(const char_t* name); + + // Remove all children + bool remove_children(); + + // Parses buffer as an XML document fragment and appends all nodes as children of the current node. + // Copies/converts the buffer, so it may be deleted or changed after the function returns. + // Note: append_buffer allocates memory that has the lifetime of the owning document; removing the appended nodes does not immediately reclaim that memory. + xml_parse_result append_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Find attribute using predicate. Returns first attribute for which predicate returned true. + template xml_attribute find_attribute(Predicate pred) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) + if (pred(attrib)) + return attrib; + + return xml_attribute(); + } + + // Find child node using predicate. Returns first child for which predicate returned true. + template xml_node find_child(Predicate pred) const + { + if (!_root) return xml_node(); + + for (xml_node node = first_child(); node; node = node.next_sibling()) + if (pred(node)) + return node; + + return xml_node(); + } + + // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true. + template xml_node find_node(Predicate pred) const + { + if (!_root) return xml_node(); + + xml_node cur = first_child(); + + while (cur._root && cur._root != _root) + { + if (pred(cur)) return cur; + + if (cur.first_child()) cur = cur.first_child(); + else if (cur.next_sibling()) cur = cur.next_sibling(); + else + { + while (!cur.next_sibling() && cur._root != _root) cur = cur.parent(); + + if (cur._root != _root) cur = cur.next_sibling(); + } + } + + return xml_node(); + } + + // Find child node by attribute name/value + xml_node find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const; + xml_node find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const; + + #ifndef PUGIXML_NO_STL + // Get the absolute node path from root as a text string. + string_t path(char_t delimiter = '/') const; + #endif + + // Search for a node by path consisting of node names and . or .. elements. + xml_node first_element_by_path(const char_t* path, char_t delimiter = '/') const; + + // Recursively traverse subtree with xml_tree_walker + bool traverse(xml_tree_walker& walker); + + #ifndef PUGIXML_NO_XPATH + // Select single node by evaluating XPath query. Returns first node from the resulting node set. + xpath_node select_node(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL) const; + xpath_node select_node(const xpath_query& query) const; + + // Select node set by evaluating XPath query + xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL) const; + xpath_node_set select_nodes(const xpath_query& query) const; + + // (deprecated: use select_node instead) Select single node by evaluating XPath query. + PUGIXML_DEPRECATED xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL) const; + PUGIXML_DEPRECATED xpath_node select_single_node(const xpath_query& query) const; + + #endif + + // Print subtree using a writer object + void print(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; + + #ifndef PUGIXML_NO_STL + // Print subtree to stream + void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; + void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; + #endif + + // Child nodes iterators + typedef xml_node_iterator iterator; + + iterator begin() const; + iterator end() const; + + // Attribute iterators + typedef xml_attribute_iterator attribute_iterator; + + attribute_iterator attributes_begin() const; + attribute_iterator attributes_end() const; + + // Range-based for support + xml_object_range children() const; + xml_object_range children(const char_t* name) const; + xml_object_range attributes() const; + + // Get node offset in parsed file/string (in char_t units) for debugging purposes + ptrdiff_t offset_debug() const; + + // Get hash value (unique for handles to the same object) + size_t hash_value() const; + + // Get internal pointer + xml_node_struct* internal_object() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs); +#endif + + // A helper for working with text inside PCDATA nodes + class PUGIXML_CLASS xml_text + { + friend class xml_node; + + xml_node_struct* _root; + + typedef void (*unspecified_bool_type)(xml_text***); + + explicit xml_text(xml_node_struct* root); + + xml_node_struct* _data_new(); + xml_node_struct* _data() const; + + public: + // Default constructor. Constructs an empty object. + xml_text(); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Check if text object is empty + bool empty() const; + + // Get text, or "" if object is empty + const char_t* get() const; + + // Get text, or the default value if object is empty + const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; + + // Get text as a number, or the default value if conversion did not succeed or object is empty + int as_int(int def = 0) const; + unsigned int as_uint(unsigned int def = 0) const; + double as_double(double def = 0) const; + float as_float(float def = 0) const; + + #ifdef PUGIXML_HAS_LONG_LONG + long long as_llong(long long def = 0) const; + unsigned long long as_ullong(unsigned long long def = 0) const; + #endif + + // Get text as bool (returns true if first character is in '1tTyY' set), or the default value if object is empty + bool as_bool(bool def = false) const; + + // Set text (returns false if object is empty or there is not enough memory) + bool set(const char_t* rhs); + + // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") + bool set(int rhs); + bool set(unsigned int rhs); + bool set(long rhs); + bool set(unsigned long rhs); + bool set(double rhs); + bool set(double rhs, int precision); + bool set(float rhs); + bool set(float rhs, int precision); + bool set(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + bool set(long long rhs); + bool set(unsigned long long rhs); + #endif + + // Set text (equivalent to set without error checking) + xml_text& operator=(const char_t* rhs); + xml_text& operator=(int rhs); + xml_text& operator=(unsigned int rhs); + xml_text& operator=(long rhs); + xml_text& operator=(unsigned long rhs); + xml_text& operator=(double rhs); + xml_text& operator=(float rhs); + xml_text& operator=(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + xml_text& operator=(long long rhs); + xml_text& operator=(unsigned long long rhs); + #endif + + // Get the data node (node_pcdata or node_cdata) for this object + xml_node data() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs); +#endif + + // Child node iterator (a bidirectional iterator over a collection of xml_node) + class PUGIXML_CLASS xml_node_iterator + { + friend class xml_node; + + private: + mutable xml_node _wrap; + xml_node _parent; + + xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent); + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_node value_type; + typedef xml_node* pointer; + typedef xml_node& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_node_iterator(); + + // Construct an iterator which points to the specified node + xml_node_iterator(const xml_node& node); + + // Iterator operators + bool operator==(const xml_node_iterator& rhs) const; + bool operator!=(const xml_node_iterator& rhs) const; + + xml_node& operator*() const; + xml_node* operator->() const; + + xml_node_iterator& operator++(); + xml_node_iterator operator++(int); + + xml_node_iterator& operator--(); + xml_node_iterator operator--(int); + }; + + // Attribute iterator (a bidirectional iterator over a collection of xml_attribute) + class PUGIXML_CLASS xml_attribute_iterator + { + friend class xml_node; + + private: + mutable xml_attribute _wrap; + xml_node _parent; + + xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent); + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_attribute value_type; + typedef xml_attribute* pointer; + typedef xml_attribute& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_attribute_iterator(); + + // Construct an iterator which points to the specified attribute + xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent); + + // Iterator operators + bool operator==(const xml_attribute_iterator& rhs) const; + bool operator!=(const xml_attribute_iterator& rhs) const; + + xml_attribute& operator*() const; + xml_attribute* operator->() const; + + xml_attribute_iterator& operator++(); + xml_attribute_iterator operator++(int); + + xml_attribute_iterator& operator--(); + xml_attribute_iterator operator--(int); + }; + + // Named node range helper + class PUGIXML_CLASS xml_named_node_iterator + { + friend class xml_node; + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_node value_type; + typedef xml_node* pointer; + typedef xml_node& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_named_node_iterator(); + + // Construct an iterator which points to the specified node + xml_named_node_iterator(const xml_node& node, const char_t* name); + + // Iterator operators + bool operator==(const xml_named_node_iterator& rhs) const; + bool operator!=(const xml_named_node_iterator& rhs) const; + + xml_node& operator*() const; + xml_node* operator->() const; + + xml_named_node_iterator& operator++(); + xml_named_node_iterator operator++(int); + + xml_named_node_iterator& operator--(); + xml_named_node_iterator operator--(int); + + private: + mutable xml_node _wrap; + xml_node _parent; + const char_t* _name; + + xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name); + }; + + // Abstract tree walker class (see xml_node::traverse) + class PUGIXML_CLASS xml_tree_walker + { + friend class xml_node; + + private: + int _depth; + + protected: + // Get current traversal depth + int depth() const; + + public: + xml_tree_walker(); + virtual ~xml_tree_walker(); + + // Callback that is called when traversal begins + virtual bool begin(xml_node& node); + + // Callback that is called for each node traversed + virtual bool for_each(xml_node& node) = 0; + + // Callback that is called when traversal ends + virtual bool end(xml_node& node); + }; + + // Parsing status, returned as part of xml_parse_result object + enum xml_parse_status + { + status_ok = 0, // No error + + status_file_not_found, // File was not found during load_file() + status_io_error, // Error reading from file/stream + status_out_of_memory, // Could not allocate memory + status_internal_error, // Internal error occurred + + status_unrecognized_tag, // Parser could not determine tag type + + status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction + status_bad_comment, // Parsing error occurred while parsing comment + status_bad_cdata, // Parsing error occurred while parsing CDATA section + status_bad_doctype, // Parsing error occurred while parsing document type declaration + status_bad_pcdata, // Parsing error occurred while parsing PCDATA section + status_bad_start_element, // Parsing error occurred while parsing start element tag + status_bad_attribute, // Parsing error occurred while parsing element attribute + status_bad_end_element, // Parsing error occurred while parsing end element tag + status_end_element_mismatch,// There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag) + + status_append_invalid_root, // Unable to append nodes since root type is not node_element or node_document (exclusive to xml_node::append_buffer) + + status_no_document_element // Parsing resulted in a document without element nodes + }; + + // Parsing result + struct PUGIXML_CLASS xml_parse_result + { + // Parsing status (see xml_parse_status) + xml_parse_status status; + + // Last parsed offset (in char_t units from start of input data) + ptrdiff_t offset; + + // Source document encoding + xml_encoding encoding; + + // Default constructor, initializes object to failed state + xml_parse_result(); + + // Cast to bool operator + operator bool() const; + + // Get error description + const char* description() const; + }; + + // Document class (DOM tree root) + class PUGIXML_CLASS xml_document: public xml_node + { + private: + char_t* _buffer; + + char _memory[192]; + + // Non-copyable semantics + xml_document(const xml_document&); + xml_document& operator=(const xml_document&); + + void _create(); + void _destroy(); + void _move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; + + public: + // Default constructor, makes empty document + xml_document(); + + // Destructor, invalidates all node/attribute handles to this document + ~xml_document(); + + #ifdef PUGIXML_HAS_MOVE + // Move semantics support + xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; + xml_document& operator=(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; + #endif + + // Removes all nodes, leaving the empty document + void reset(); + + // Removes all nodes, then copies the entire contents of the specified document + void reset(const xml_document& proto); + + #ifndef PUGIXML_NO_STL + // Load document from stream. + xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default); + #endif + + // (deprecated: use load_string instead) Load document from zero-terminated string. No encoding conversions are applied. + PUGIXML_DEPRECATED xml_parse_result load(const char_t* contents, unsigned int options = parse_default); + + // Load document from zero-terminated string. No encoding conversions are applied. + xml_parse_result load_string(const char_t* contents, unsigned int options = parse_default); + + // Load document from file + xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer. Copies/converts the buffer, so it may be deleted or changed after the function returns. + xml_parse_result load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). + // You should ensure that buffer data will persist throughout the document's lifetime, and free the buffer memory manually once document is destroyed. + xml_parse_result load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). + // You should allocate the buffer with pugixml allocation function; document will free the buffer when it is no longer needed (you can't use it anymore). + xml_parse_result load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Save XML document to writer (semantics is slightly different from xml_node::print, see documentation for details). + void save(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + + #ifndef PUGIXML_NO_STL + // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details). + void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; + #endif + + // Save XML to file + bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + + // Get document element + xml_node document_element() const; + }; + +#ifndef PUGIXML_NO_XPATH + // XPath query return type + enum xpath_value_type + { + xpath_type_none, // Unknown type (query failed to compile) + xpath_type_node_set, // Node set (xpath_node_set) + xpath_type_number, // Number + xpath_type_string, // String + xpath_type_boolean // Boolean + }; + + // XPath parsing result + struct PUGIXML_CLASS xpath_parse_result + { + // Error message (0 if no error) + const char* error; + + // Last parsed offset (in char_t units from string start) + ptrdiff_t offset; + + // Default constructor, initializes object to failed state + xpath_parse_result(); + + // Cast to bool operator + operator bool() const; + + // Get error description + const char* description() const; + }; + + // A single XPath variable + class PUGIXML_CLASS xpath_variable + { + friend class xpath_variable_set; + + protected: + xpath_value_type _type; + xpath_variable* _next; + + xpath_variable(xpath_value_type type); + + // Non-copyable semantics + xpath_variable(const xpath_variable&); + xpath_variable& operator=(const xpath_variable&); + + public: + // Get variable name + const char_t* name() const; + + // Get variable type + xpath_value_type type() const; + + // Get variable value; no type conversion is performed, default value (false, NaN, empty string, empty node set) is returned on type mismatch error + bool get_boolean() const; + double get_number() const; + const char_t* get_string() const; + const xpath_node_set& get_node_set() const; + + // Set variable value; no type conversion is performed, false is returned on type mismatch error + bool set(bool value); + bool set(double value); + bool set(const char_t* value); + bool set(const xpath_node_set& value); + }; + + // A set of XPath variables + class PUGIXML_CLASS xpath_variable_set + { + private: + xpath_variable* _data[64]; + + void _assign(const xpath_variable_set& rhs); + void _swap(xpath_variable_set& rhs); + + xpath_variable* _find(const char_t* name) const; + + static bool _clone(xpath_variable* var, xpath_variable** out_result); + static void _destroy(xpath_variable* var); + + public: + // Default constructor/destructor + xpath_variable_set(); + ~xpath_variable_set(); + + // Copy constructor/assignment operator + xpath_variable_set(const xpath_variable_set& rhs); + xpath_variable_set& operator=(const xpath_variable_set& rhs); + + #ifdef PUGIXML_HAS_MOVE + // Move semantics support + xpath_variable_set(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT; + xpath_variable_set& operator=(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT; + #endif + + // Add a new variable or get the existing one, if the types match + xpath_variable* add(const char_t* name, xpath_value_type type); + + // Set value of an existing variable; no type conversion is performed, false is returned if there is no such variable or if types mismatch + bool set(const char_t* name, bool value); + bool set(const char_t* name, double value); + bool set(const char_t* name, const char_t* value); + bool set(const char_t* name, const xpath_node_set& value); + + // Get existing variable by name + xpath_variable* get(const char_t* name); + const xpath_variable* get(const char_t* name) const; + }; + + // A compiled XPath query object + class PUGIXML_CLASS xpath_query + { + private: + void* _impl; + xpath_parse_result _result; + + typedef void (*unspecified_bool_type)(xpath_query***); + + // Non-copyable semantics + xpath_query(const xpath_query&); + xpath_query& operator=(const xpath_query&); + + public: + // Construct a compiled object from XPath expression. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on compilation errors. + explicit xpath_query(const char_t* query, xpath_variable_set* variables = PUGIXML_NULL); + + // Constructor + xpath_query(); + + // Destructor + ~xpath_query(); + + #ifdef PUGIXML_HAS_MOVE + // Move semantics support + xpath_query(xpath_query&& rhs) PUGIXML_NOEXCEPT; + xpath_query& operator=(xpath_query&& rhs) PUGIXML_NOEXCEPT; + #endif + + // Get query expression return type + xpath_value_type return_type() const; + + // Evaluate expression as boolean value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + bool evaluate_boolean(const xpath_node& n) const; + + // Evaluate expression as double value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + double evaluate_number(const xpath_node& n) const; + + #ifndef PUGIXML_NO_STL + // Evaluate expression as string value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + string_t evaluate_string(const xpath_node& n) const; + #endif + + // Evaluate expression as string value in the specified context; performs type conversion if necessary. + // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero). + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + // If PUGIXML_NO_EXCEPTIONS is defined, returns empty set instead. + size_t evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const; + + // Evaluate expression as node set in the specified context. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. + // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead. + xpath_node_set evaluate_node_set(const xpath_node& n) const; + + // Evaluate expression as node set in the specified context. + // Return first node in document order, or empty node if node set is empty. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. + // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node instead. + xpath_node evaluate_node(const xpath_node& n) const; + + // Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode) + const xpath_parse_result& result() const; + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + }; + + #ifndef PUGIXML_NO_EXCEPTIONS + #if defined(_MSC_VER) + // C4275 can be ignored in Visual C++ if you are deriving + // from a type in the Standard C++ Library + #pragma warning(push) + #pragma warning(disable: 4275) + #endif + // XPath exception class + class PUGIXML_CLASS xpath_exception: public std::exception + { + private: + xpath_parse_result _result; + + public: + // Construct exception from parse result + explicit xpath_exception(const xpath_parse_result& result); + + // Get error message + virtual const char* what() const throw() PUGIXML_OVERRIDE; + + // Get parse result + const xpath_parse_result& result() const; + }; + #if defined(_MSC_VER) + #pragma warning(pop) + #endif + #endif + + // XPath node class (either xml_node or xml_attribute) + class PUGIXML_CLASS xpath_node + { + private: + xml_node _node; + xml_attribute _attribute; + + typedef void (*unspecified_bool_type)(xpath_node***); + + public: + // Default constructor; constructs empty XPath node + xpath_node(); + + // Construct XPath node from XML node/attribute + xpath_node(const xml_node& node); + xpath_node(const xml_attribute& attribute, const xml_node& parent); + + // Get node/attribute, if any + xml_node node() const; + xml_attribute attribute() const; + + // Get parent of contained node/attribute + xml_node parent() const; + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators + bool operator==(const xpath_node& n) const; + bool operator!=(const xpath_node& n) const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs); +#endif + + // A fixed-size collection of XPath nodes + class PUGIXML_CLASS xpath_node_set + { + public: + // Collection type + enum type_t + { + type_unsorted, // Not ordered + type_sorted, // Sorted by document order (ascending) + type_sorted_reverse // Sorted by document order (descending) + }; + + // Constant iterator type + typedef const xpath_node* const_iterator; + + // We define non-constant iterator to be the same as constant iterator so that various generic algorithms (i.e. boost foreach) work + typedef const xpath_node* iterator; + + // Default constructor. Constructs empty set. + xpath_node_set(); + + // Constructs a set from iterator range; data is not checked for duplicates and is not sorted according to provided type, so be careful + xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted); + + // Destructor + ~xpath_node_set(); + + // Copy constructor/assignment operator + xpath_node_set(const xpath_node_set& ns); + xpath_node_set& operator=(const xpath_node_set& ns); + + #ifdef PUGIXML_HAS_MOVE + // Move semantics support + xpath_node_set(xpath_node_set&& rhs) PUGIXML_NOEXCEPT; + xpath_node_set& operator=(xpath_node_set&& rhs) PUGIXML_NOEXCEPT; + #endif + + // Get collection type + type_t type() const; + + // Get collection size + size_t size() const; + + // Indexing operator + const xpath_node& operator[](size_t index) const; + + // Collection iterators + const_iterator begin() const; + const_iterator end() const; + + // Sort the collection in ascending/descending order by document order + void sort(bool reverse = false); + + // Get first node in the collection by document order + xpath_node first() const; + + // Check if collection is empty + bool empty() const; + + private: + type_t _type; + + xpath_node _storage[1]; + + xpath_node* _begin; + xpath_node* _end; + + void _assign(const_iterator begin, const_iterator end, type_t type); + void _move(xpath_node_set& rhs) PUGIXML_NOEXCEPT; + }; +#endif + +#ifndef PUGIXML_NO_STL + // Convert wide string to UTF8 + std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const wchar_t* str); + std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const std::basic_string, std::allocator >& str); + + // Convert UTF8 to wide string + std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const char* str); + std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const std::basic_string, std::allocator >& str); +#endif + + // Memory allocation function interface; returns pointer to allocated memory or NULL on failure + typedef void* (*allocation_function)(size_t size); + + // Memory deallocation function interface + typedef void (*deallocation_function)(void* ptr); + + // Override default memory management functions. All subsequent allocations/deallocations will be performed via supplied functions. + void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate); + + // Get current memory management functions + allocation_function PUGIXML_FUNCTION get_memory_allocation_function(); + deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function(); +} + +#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) +namespace std +{ + // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&); +} +#endif + +#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) +namespace std +{ + // Workarounds for (non-standard) iterator category detection + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&); +} +#endif + +#endif + +// Make sure implementation is included in header-only mode +// Use macro expansion in #include to work around QMake (QTBUG-11923) +#if defined(PUGIXML_HEADER_ONLY) && !defined(PUGIXML_SOURCE) +# define PUGIXML_SOURCE "pugixml.cpp" +# include PUGIXML_SOURCE +#endif + +/** + * Copyright (c) 2006-2022 Arseny Kapoulkine + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ diff --git a/MiaoUI/src/source/User/Mui_Engine.cpp b/MiaoUI/src/source/User/Mui_Engine.cpp new file mode 100644 index 0000000..b128eb1 --- /dev/null +++ b/MiaoUI/src/source/User/Mui_Engine.cpp @@ -0,0 +1,367 @@ +/** + * FileName: Mui_Engine.cpp + * Note: 引擎主类封装实现 + * + * Copyright (C) 2022-2023 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2022-12-28 Create +*/ +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +UINT_PTR g_gdiplusToken = 0; + +namespace Mui +{ +#ifdef _WIN32 + + bool GetWndMonitorDPI(HWND hWnd, UINT& dpiX, UINT& dpiY) + { + //Windows 8.1及以上可用 + static HINSTANCE _dll = LoadLibraryW(L"SHCore.dll"); + if (_dll) + { + typedef HRESULT(WINAPI* _fun)(HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*); + if (_fun GetDpiForMonitor_ = (_fun)GetProcAddress(_dll, "GetDpiForMonitor")) + { + if (FAILED(GetDpiForMonitor_(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), + MDT_EFFECTIVE_DPI, &dpiX, &dpiY))) + goto gdiget; + return true; + } + } + + //旧版系统 使用gdi获取主桌面DPI + gdiget: + HDC hdc = GetDC(NULL); + if (hdc) + { + dpiX = GetDeviceCaps(hdc, LOGPIXELSX); + dpiY = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(NULL, hdc); + return true; + } + return false; + } + + class WindowsCtx final : public Window::UIWindowsWnd + { + public: + explicit WindowsCtx(Render::MRender* render) : UIWindowsWnd(render) + { + m_defsource = [this](auto&& PH1, auto&& PH2) -> _m_result + { + if((DWORD)PH1 == WM_DPICHANGED && m_enabledpi) + { + thread_local bool flag = false; + if (flag) + return 0; + flag = true; + const auto pm = (std::pair<_m_param, _m_param>*)PH2; + const UINT dpiX = Helper::M_LOWORD((_m_long)pm->first); + float scale = (float)dpiX / 96.f; + ScaleWindow({ scale, scale }); + flag = false; + return 0; + } + if ((DWORD)PH1 == WM_DESTROY) + { + delete m_src; + return 0; + } + + return UIWindowBasic::EventSource(std::forward(PH1), std::forward(PH2)); + }; + } + + private: + bool EventProc(UINotifyEvent event, Ctrl::UIControl* control, _m_param param) override + { + if (m_event) + { + if ( m_event(m_src, event, control, param)) + return true; + } + return false; + } + + _m_result EventSource(MEventCodeEnum code, _m_param param) override + { + if (m_source) + return m_source(m_src, m_defsource, code, param); + return m_defsource(code, param); + } + + MWndDefEventSource m_defsource = nullptr; + MWndEventCallback m_event = nullptr; + MWndSourceCallback m_source = nullptr; + + bool m_enabledpi = false; + UIRect m_srcRect; + MWindowCtx* m_src = nullptr; + + friend class MiaoUI; + friend class MWindowCtx; + }; +#endif + + MiaoUI::~MiaoUI() + { + if(m_isinit) + UnInitEngine(); + } + + bool MiaoUI::InitEngine(std::wstring& error, Render render, _m_ptrv param) + { + if (m_isinit) return false; + + CtrlMgr::RegisterMuiControl(); + m_isinit = true; + m_renderType = render; + if (render == Render::Auto || render == Render::Gdiplus) + { + CoInitialize(nullptr); + Gdiplus::GdiplusStartupInput gdiplusStartupInput; + Gdiplus::GdiplusStartup(&g_gdiplusToken, &gdiplusStartupInput, nullptr); + } + else if(render == Render::Custom) + { + if (!param) + { + error = L"param error"; + return false; + } + m_customRender = param; + } + return true; + } + + void MiaoUI::UnInitEngine() + { + if (!m_isinit) return; + m_isinit = false; + for (size_t i = 0; i < m_windowList.size(); i++) + { + delete m_windowList[i]; + } + for (size_t i = 0; i < m_res_memList.size(); i++) + { + m_res_memList[i].first.Release(); + } + if (m_renderType == Render::Gdiplus) + Gdiplus::GdiplusShutdown(g_gdiplusToken); + } + + bool MiaoUI::AddResourcePath(std::wstring_view path, std::wstring_view key) + { + DMResources res; + if(!res.LoadResource(path.data(), false)) + { + res.CloseResource(); + return false; + } + res.CloseResource(); + for (auto& wnd : m_windowList) + wnd->Base()->GetResourceMgr()->AddResourcePath(path, key); + return m_res_pathList.insert(std::make_pair(path, key)).second; + } + + bool MiaoUI::AddResourceMem(UIResource memfile, std::wstring_view key) + { + for(auto& mem : m_res_memList) + { + if (mem.first.size == memfile.size && + memcmp(mem.first.data, memfile.data, memfile.size) != 0) + return false; + } + DMResources res; + if(!res.LoadResource(memfile)) + { + res.CloseResource(); + return false; + } + res.CloseResource(); + + UIResource dst(new(std::nothrow)_m_byte[memfile.size], memfile.size); + if (!dst.data) + return false; + + memcpy(dst.data, memfile.data, memfile.size); + + for (auto& wnd : m_windowList) + wnd->Base()->GetResourceMgr()->AddResourceMem(dst, key); + + m_res_memList.emplace_back(dst, key); + return true; + } + + bool MiaoUI::RemoveResource(std::wstring_view path) + { + bool del = false; + for(auto& wnd : m_windowList) + { + bool success = wnd->Base()->GetResourceMgr()->RemoveResource(path); + if (!del && success) + del = true; + } + for (auto iter = m_res_pathList.begin(); iter != m_res_pathList.end(); ++iter) + { + if (iter->first == path) + { + m_res_pathList.erase(iter); + break; + } + } + return del; + } + + MWindowCtx* MiaoUI::CreateWindowCtx(UIRect rect, MWindowType type, std::wstring_view title, + bool main, bool dpi, _m_param parent, _m_ptrv exparam) + { + auto ret = new MWindowCtx(); +#ifdef _WIN32 + const auto wnd = new WindowsCtx(CreateRender()); + if(!wnd->Create(parent, title, rect, [] { return true; }, (int)type, (_m_param)exparam)) + { + delete ret; + delete wnd; + return nullptr; + } + ret->m_base = wnd; + ret->m_base->SetInited(false); + + auto mgr = wnd->GetResourceMgr(); + for (auto& path : m_res_pathList) + mgr->AddResourcePath(path.first, path.second); + for (auto& mem : m_res_memList) + mgr->AddResourceMem(mem.first, mem.second); + + wnd->m_enabledpi = dpi; + wnd->m_srcRect = rect; + wnd->m_src = ret; + ret->m_engine = this; + + m_windowList.push_back(ret); + + if (main) + wnd->SetMainWindow(true); + + return ret; +#else +#error __TODO__ +#endif + } + + Render::MRender* MiaoUI::CreateRender() + { +#ifdef _WIN32 + if (m_renderType == Render::Gdiplus || m_renderType == Render::Auto) + return new Mui::Render::MRender_GDIP(); +#if MUI_CFG_ENABLE_OPENGL + if (m_renderType == Render::OpenGL_Core) + return new Mui::Render::GL::MRender_GL(); +#endif +#elif __ANDROID__ + if(m_renderType == Render::OpenGL_ES) + return new MRender_GL(); +#else +#error __TODO__ +#endif + if (m_renderType == Render::Custom && m_customRender) + return (Mui::Render::MRender*)m_customRender; + + MErrorThrow(L"没有可用的渲染器(There is no renderer available.)"); + return nullptr; + } + + MWindowCtx::~MWindowCtx() + { + delete m_base; + for (_m_long64 i = 0; i < (_m_long64)m_engine->m_windowList.size(); ++i) + { + if(m_engine->m_windowList[i] == this) + { + m_engine->m_windowList.erase(m_engine->m_windowList.begin() + i); + return; + } + } + } + + Window::UIWindowBasic* MWindowCtx::Base() const + { + return m_base; + } + + XML::MuiXML* MWindowCtx::XML() const + { + return m_base->XMLUI(); + } + + bool MWindowCtx::InitWindow(const MWndInitUICallback& callback, bool visible) + { + if(!m_isInit) + { + m_isInit = callback(this, m_base->GetRootControl(), m_base->XMLUI()); + if (m_isInit) + { +#ifdef _WIN32 + if (auto p = static_cast(m_base); p->m_enabledpi) + { + //DPI缩放 + UINT dpiX, dpiY = 0; + if (GetWndMonitorDPI((HWND)m_base->GetWindowHandle(), dpiX, dpiY)) + { + float scaleX = (float)dpiX / 96.f; + float scaleY = (float)dpiY / 96.f; + + m_base->ScaleWindow({ scaleX, scaleY }); + } + } +#else +#error __TODO__ +#endif + m_base->ShowWindow(visible); + } + m_base->SetInited(m_isInit); + m_base->m_updateCache = true; + m_base->UpdateLayout(nullptr); + return m_isInit; + } + return false; + } + + void MWindowCtx::SetEventCallback(MWndEventCallback callback) + { + dynamic_cast(m_base)->m_event = std::move(callback); + } + + void MWindowCtx::SetEventSourceCallback(MWndSourceCallback callback) + { + dynamic_cast(m_base)->m_source = std::move(callback); + } + + void MWindowCtx::EventLoop() + { + Settings::UIMessageLoop(); + } + + MWindowCtx::MWindowCtx() = default; +} diff --git a/MiaoUI/src/source/Window/Mui_BasicWnd.cpp b/MiaoUI/src/source/Window/Mui_BasicWnd.cpp new file mode 100644 index 0000000..fbd368a --- /dev/null +++ b/MiaoUI/src/source/Window/Mui_BasicWnd.cpp @@ -0,0 +1,578 @@ +/** + * FileName: Mui_BasicWnd.cpp + * Note: UI窗口容器实现 + * + * Copyright (C) 2021-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2021-4-28 Create +*/ +#include +#include +#include +#include + +namespace Mui::Window +{ + UIWindowBasic::UIWindowBasic(Render::Def::MRender* render) + : MThreadT([this] { ThreadProc(); }), m_rootBox(new Ctrl::UIControl()), m_drawCmdList(255) + { + m_render = render; + m_renderCmd = new Render::MRenderCmd(render); + m_resourceMgr = new UIResourceMgr(m_renderCmd); + + m_timer = new MTimers(); + + m_rootBox->m_render = m_renderCmd; + m_rootBox->SetAlignType(UIAlignment_Absolute, false); + m_rootBox->UINodeBase::m_data.ParentWnd = this; + + m_renderRoot = new Render::MNodeRoot(m_rootBox); + + Render::UINodeBase::PosSizeUnit unit; + unit.x_w = Render::UINodeBase::Percentage; + unit.y_h = unit.x_w; + + m_root = new Ctrl::UIControl(); + m_rootBox->AddChildren(m_root); + m_root->SetAlignType(UIAlignment_Absolute, false); + m_root->SetSize(100, 100, false); + m_root->SetSizeUnit(unit, false); + m_root->AutoSize(false, false); + + m_toplay = new Ctrl::UIControl(); + m_rootBox->AddChildren(m_toplay); + m_toplay->SetAlignType(UIAlignment_Absolute, false); + m_toplay->SetSize(100, 100, false); + m_toplay->SetSizeUnit(unit, false); + m_toplay->SetMsgIgnore(true, false); + m_toplay->AutoSize(false, false); + + m_xmlUI = new XML::MuiXML(this); + + Start(true); + } + + UIWindowBasic::~UIWindowBasic() + { + Stop(); + m_dbgFrame = nullptr; + delete m_xmlUI; + delete m_rootBox; + delete m_renderRoot; + delete m_timer; + delete m_resourceMgr; + m_render->Release(); + delete m_renderCmd; + } + + Ctrl::UIControl* UIWindowBasic::GetRootControl() const + { + return m_root; + } + + void UIWindowBasic::UpdateLayout(MPCRect rect) + { + m_renderCmd->RunTask([this] + { + const UIRect&& rcClient = GetWindowRect(true); + auto ctrl = m_rootBox; + ctrl->SetSize(rcClient.GetWidth(), rcClient.GetHeight(), false); + ctrl->UINodeBase::m_data.Frame = rcClient.ToRectT(); + ctrl->UINodeBase::m_data.ClipFrame = rcClient.ToRectT(); + ctrl->UINodeBase::m_data.Align.Layout(ctrl, 0); + ctrl->UpdateDisplay(); + }); + } + + void UIWindowBasic::UpdateDisplay(MPCRect rect) + { + if (m_renderMode) return; + _m_rect updateRect; + if (rect) + updateRect = *rect; + if(!m_drawCmdList.isFull()) + m_drawCmdList.push({ updateRect.left, updateRect.top, updateRect.right, updateRect.bottom }); + ResumeThread(); + } + + void UIWindowBasic::SetRenderMode(bool active) + { + m_renderMode = active; + ResumeThread(); + } + + bool UIWindowBasic::GetRenderMode() const + { + return m_renderMode; + } + + void UIWindowBasic::ScaleWindow(_m_uint newWidth, _m_uint newHeight) + { + _m_scale newSize; + UISize src = GetWindowSrcSize(); + newSize.cx = (float)newWidth / (float)src.width; + newSize.cy = (float)newHeight / (float)src.height; + ScaleWindow(newSize); + } + + void UIWindowBasic::ScaleWindow(_m_scale scale) + { + UISize src = GetWindowSrcSize(); + UIRect rect = GetWindowRect(); + rect.right = rect.left + int((float)src.width * scale.cx); + rect.bottom = rect.top + int((float)src.height * scale.cy); + SetWindowRect(rect); + m_scale = scale; + UpdateScale(); + } + + _m_scale UIWindowBasic::GetWindowScale() const + { + return m_scale; + } + + MTimers::ID UIWindowBasic::SetTimer(_m_uint elapse, const MTimerCallback& callback) + { + auto task = [callback, this](_m_ptrv id, _m_ulong time) + { + if (callback) callback((_m_ptrv)this, id, time); + EventSource(M_WND_TIMER, (_m_param)id); + }; + return m_timer->AddTimer(elapse, task); + } + + void UIWindowBasic::KillTimer(MTimers::ID id) + { + m_timer->DelTimer(id); + } + + void UIWindowBasic::SetMaxFPSLimit(int fps) + { + m_fpsCounter.SetMaxFPS((float)fps); + } + + _m_uint UIWindowBasic::GetLastFPS() const + { + return m_fpsCache; + } + + void UIWindowBasic::SetMainWindow(bool main) + { + m_isMainWnd = main; + } + + bool UIWindowBasic::IsMainWindow() const + { + return m_isMainWnd; + } + + void UIWindowBasic::SetCacheMode(bool cache) + { + m_cacheRes = cache; + UpdateDisplay(nullptr); + } + + bool UIWindowBasic::GetResMode() + { + return m_cacheRes; + } + + Render::MRenderCmd* UIWindowBasic::GetRender() const + { + return m_renderCmd; + } + + UIResourceMgr* UIWindowBasic::GetResourceMgr() const + { + return m_resourceMgr; + } + + UIFocus UIWindowBasic::GetFocusControl() const + { + return m_focus; + } + + Ctrl::UIControl* UIWindowBasic::FindTopLevelControl(std::wstring_view name) const + { + return m_toplay->FindChildren(name); + } + + void UIWindowBasic::ShowDebugRect(bool show) + { + ExecuteThreadTask([&] + { + if (!show) + m_dbgFrame = nullptr; + else if (!m_dbgFrame) + { + m_dbgFrame = m_renderCmd->CreatePen(1, Color::M_RED); + } + + if (!m_renderMode) + RenderControlTree(nullptr); + }); + } + + void UIWindowBasic::ShowHighlight(bool show) + { + m_highlight = show; + UpdateDisplay(nullptr); + } + + void UIWindowBasic::UpdateCache() + { + m_updateCache = true; + UpdateLayout(nullptr); + } + + void UIWindowBasic::SetFocusControl(Ctrl::UIControl* control) + { + m_focus = { control }; + if (m_dbgFrame && m_highlight) + control->UpdateDisplay(); + } + + void UIWindowBasic::SetCapture(Ctrl::UIControl* control) + { + m_capture = control; +#ifdef _WIN32 + ::SetCapture((HWND)GetWindowHandle()); +#endif // _WIN32 + + } + + void UIWindowBasic::ReleaseCapture() + { + m_capture = nullptr; +#ifdef _WIN32 + ::ReleaseCapture(); +#endif // _WIN32 + + } + + _m_result UIWindowBasic::EventSource(MEventCodeEnum code, _m_param param) + { + bool result = false; + switch (code) + { + case M_WND_CLOSE: + { + SetRenderMode(false); + MThreadT::Stop(); + break; + } + case M_WND_PAINT: + { + UpdateDisplay(nullptr); + result = true; + break; + } + case M_WND_SIZE: + { + m_renderCmd->RunTask([this] + { + auto rc = GetWindowRect(true); + m_render->Resize(rc.GetWidth(), rc.GetHeight()); + //更新布局 + UpdateLayout(nullptr); + m_updateCache = true; + }); + } + break; + case M_MOUSE_LEAVE: + case M_MOUSE_MOVE: + case M_MOUSE_LBDOWN: + case M_MOUSE_LBUP: + case M_MOUSE_RBDOWN: + case M_MOUSE_RBUP: + case M_MOUSE_MBDOWN: + case M_MOUSE_MBUP: + case M_MOUSE_LBDBCLICK: + case M_MOUSE_RBDBCLICK: + case M_MOUSE_MBDBCLICK: + case M_MOUSE_WHEEL: + case M_SETCURSOR: + { + if (m_capture) + result = DispatchControlMessage(m_capture, code, param, true); + else + result = EventMouseProc(code, param); + } + break; + default: + { + const auto focus = m_focus.curFocus; + if (code != M_WND_SETFOCUS && code != M_WND_KILLFOCUS) + { + if (m_capture) + result = DispatchControlMessage(m_capture, code, param, false); + else if (focus) + { + DispatchControlMessage(focus, code, param, false); + } + break; + } + if (code == M_WND_KILLFOCUS) + { + if (focus != nullptr) + focus->SetFocus(false); + m_focus = { nullptr }; + } + } + break; + } + + return result; + } + + bool UIWindowBasic::DispatchControlMessage(Ctrl::UIControl* control, MEventCodeEnum code, _m_param param, bool mouse) + { + const auto pm = (std::pair<_m_param, _m_param>*)param; + if (control && pm) + { + if (mouse) + return control->DispatchMouseMessage(code, pm->first, pm->second); + return control->DispatchWindowMessage(code, pm->first, pm->second); + } + return false; + } + + bool UIWindowBasic::EventMouseProc(MEventCodeEnum code, _m_param param) + { + const auto pm = (std::pair<_m_param, _m_param>*)param; + + //设置当前鼠标控件 + auto SetMouseControl = [this, param](Ctrl::UIControl* ctrl) + { + if (m_mouseCur == ctrl) return; + + //对旧控件分发离开消息 + if (m_mouseCur) + DispatchControlMessage(m_mouseCur, M_MOUSE_LEAVE, param, true); + + //对新控件分发进入消息 + if (ctrl) + DispatchControlMessage(ctrl, M_MOUSE_HOVER, param, true); + + m_mouseCur = ctrl; + }; + + switch (code) + { + case M_MOUSE_LEAVE: + SetMouseControl(nullptr); + m_mouseIn = false; + return true; + case M_MOUSE_LBDOWN: +#ifdef _WIN32 + ::SetFocus((HWND)GetWindowHandle()); +#else + EventSource(M_WND_SETFOCUS, param); +#endif // _WIN32 + break; + default: + break; + } + + if (m_rootBox) + { + const UIPoint point = { (int)(short)Helper::M_LOWORD((_m_long)pm->second), (int)(short)Helper::M_HIWORD((_m_long)pm->second) }; + Ctrl::UIControl* curControl = m_rootBox->FindMouseControl(point); + + if (code == M_MOUSE_MOVE) + { +#ifdef _WIN32 + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = (HWND)GetWindowHandle(); + TrackMouseEvent(&tme); +#endif + + SetMouseControl(curControl); + } + else if (code == M_SETCURSOR) + { + curControl = m_mouseCur; + if (!curControl) return false; + } + + return DispatchControlMessage(curControl, code, param, true); + } + + return false; + } + + void UIWindowBasic::SetInited(bool inited) + { + m_inited = inited; + auto&& ctrl = m_rootBox; + ctrl->UINodeBase::m_data.Frame = GetWindowRect(true).ToRectT(); + } + + void UIWindowBasic::UpdateScale() + { + if (m_rootBox) + { + auto ctrl = m_rootBox; + ctrl->OnScale(m_scale); + } + UpdateLayout(nullptr); + } + + void UIWindowBasic::LoadNodeResource(bool recreate) + { + std::function loadResource + = [&](Ctrl::UINodeBase* node) + { + if (!recreate) + node->InitDeviceResource(); + else + node->OnLoadResource(m_renderCmd, true); + std::vector nodeList; + node->GetChildrenList(nodeList); + for (const auto& _node : nodeList) + { + loadResource(_node); + } + }; + loadResource(m_rootBox); + } + + void UIWindowBasic::RenderControlTree(const _m_rect_t* dirtyArea) + { + if (!m_inited || IsMinimize()) return; + + m_fpsCounter.LimitFPS(); + + m_renderCmd->RunTask([&] + { + const UIRect&& rcClient = GetWindowRect(true); + const int cvWidth = rcClient.GetWidth(); + const int cvHeight = rcClient.GetHeight(); + + if (cvWidth == 0 || cvHeight == 0) + { + return; + } + + _m_rect dirtyAreaRect; + if (dirtyArea) + { + dirtyAreaRect = *dirtyArea; + //限定更新区域不能超过画布尺寸 + if (dirtyArea->left < 0) dirtyAreaRect.left = 0; + if (dirtyArea->top < 0) dirtyAreaRect.top = 0; + if (dirtyArea->right > cvWidth || dirtyArea->right < 0) + dirtyAreaRect.right = cvWidth; + if (dirtyArea->bottom > cvHeight || dirtyArea->bottom < 0) + dirtyAreaRect.bottom = cvHeight; + } + m_renderCmd->SetCanvas(m_renderCmd->GetRenderCanvas()); + m_renderCmd->BeginDraw(); + //清空脏区域 + if (!dirtyArea && !m_renderMode) + { + m_renderCmd->PushClipRect(dirtyAreaRect); + m_renderCmd->Clear(); + m_renderCmd->PopClipRect(); + } + else + m_renderCmd->Clear(); + + //绘制子控件 + renderData _param; + try + { + m_renderRoot->RenderTree(&_param); + } + catch(...) + { + m_renderCmd->EndDraw(); + std::rethrow_exception(std::current_exception()); + } + m_updateCache = false; + + //由平台接口实现呈现 + Present(m_renderCmd, &dirtyAreaRect); + }); + + //记录当前FPS + m_fpsCache = m_fpsCounter.CalcFPS(); + } + + void UIWindowBasic::ThreadProc() try + { + //绘制任务 + _m_rect_t dirtyArea { 0 }; + if (!m_drawCmdList.isEmpty() && !m_renderMode) + m_drawCmdList.pop(dirtyArea); + RenderControlTree(&dirtyArea); + + using namespace std::chrono; + std::unique_lock lock = GetLock(); + if (!m_renderMode && m_drawCmdList.isEmpty() && m_threadTaskList.empty()) + { + MThreadT::Pause(lock); + } + + //线程任务 + for (auto& [task, notification] : m_threadTaskList) + { + try + { + task(); + notification.set_value(); + } + catch (...) { notification.set_exception(std::current_exception()); } + } + m_threadTaskList.clear(); + } + catch(...) + { + M_ThreadPostException(std::current_exception()); + } + + void UIWindowBasic::FreeCurMouseCtrl() + { + m_mouseCur = nullptr; + m_mouseIn = false; + } + + void UIWindowBasic::ExecuteThreadTask(const std::function& task) + { + if (MThreadT::GetID() == std::this_thread::get_id()) + { + task(); + return; + } + + std::unique_lock lock = GetLock(); + + std::promise send_notification; + auto wait_notification = send_notification.get_future(); + m_threadTaskList.push_back({ task, send_notification }); + lock.unlock(); + + ResumeThread(); + wait_notification.wait(); + } + + void UIWindowBasic::ResumeThread() + { + if (!m_renderCmd->IsTaskThread()) + Resume(); + } +} diff --git a/MiaoUI/src/source/Window/Mui_Windows.cpp b/MiaoUI/src/source/Window/Mui_Windows.cpp new file mode 100644 index 0000000..a1cdea6 --- /dev/null +++ b/MiaoUI/src/source/Window/Mui_Windows.cpp @@ -0,0 +1,555 @@ +/** + * FileName: Mui_Windows.cpp + * Note: UI Windows平台窗口实现 + * + * Copyright (C) 2021-2024 Maplespe (mapleshr@icloud.com) + * + * This file is part of MiaoUI library. + * MiaoUI library is free software: you can redistribute it and/or modify it under the terms of the + * GNU Lesser General Public License as published by the Free Software Foundation, either version 3 + * of the License, or any later version. + * + * MiaoUI library 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with Foobar. + * If not, see . + * + * date: 2021-4-22 Create +*/ +#ifdef _WIN32 +#include +#include +#include + +#include + +#include +#include +#pragma comment(lib, "uxtheme.lib") +#pragma comment(lib, "Msimg32.lib") + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +namespace Mui::Window +{ + using namespace std::chrono; + using namespace Render; + + bool regwindow = false; + + UIWindowsWnd::UIWindowsWnd(MRender* render) + : UIWindowBasic(render) + { + SetInited(false); + } + + UIWindowsWnd::~UIWindowsWnd() + { + if (!m_hWnd) + return; + + SetWindowLongPtrW(m_hWnd, GWLP_USERDATA, 0); + if (UIWindowBasic::IsMainWindow()) + { + UnregisterClassW(M_DEF_CLSNAME.data(), (HINSTANCE)&__ImageBase); + regwindow = false; + } + } + + bool UIWindowsWnd::Create(_m_param parent, std::wstring_view title, UIRect rect, + std::function&& afterCreated, _m_param style, _m_param exStyle) + { + if (IsWindow(m_hWnd)) return false; + if (!regwindow) + { + regwindow = RegisterWindowClass(); + if (!regwindow) + return false; + } + + if (exStyle & WS_EX_LAYERED) + m_layerWnd = true; + + int width = rect.GetWidth(); + int height = rect.GetHeight(); + + m_hWnd = CreateWindowExW((DWORD)exStyle, M_DEF_CLSNAME.data(), title.data(), (DWORD)style, rect.left, rect.top, width, + height, (HWND)parent, 0, 0, (LPVOID)this); + + if (m_hWnd) + { + m_srcSize = { width, height }; + + m_title = title; + + //初始化渲染器 + bool ret = InitRender(GetRender()); + if (ret) + ret = afterCreated(); + if (ret) + { + SetInited(true); + UpdateLayout(nullptr); + } + return ret; + } + + return false; + } + + bool UIWindowsWnd::Create(_m_param parent, std::wstring_view title, UIRect rect, std::function&& afterCreated, int wndType, _m_param exStyle) + { + DWORD style = 0; + if (wndType == 0) style = WS_OVERLAPPEDWINDOW; + else if (wndType == 1) + { + style = WS_MINIMIZEBOX; + m_notitleWnd = true; + } + else if (wndType == 2) style = WS_POPUP; + else if (wndType == 3) + { + style = WS_POPUP; + exStyle |= WS_EX_LAYERED; + } + + return Create(parent, title, rect, std::move(afterCreated), (_m_param)style, (_m_param)exStyle); + } + + void UIWindowsWnd::SetWindowTitle(std::wstring_view title) + { + m_title = title; + ::SetWindowTextW(m_hWnd, title.data()); + } + + std::wstring UIWindowsWnd::GetWindowTitle() + { + return m_title; + } + + void UIWindowsWnd::ShowWindow(bool show, bool focus) + { + ::ShowWindow(m_hWnd, show ? (focus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE); + } + + bool UIWindowsWnd::IsShowWindow() + { + return ::IsWindowVisible(m_hWnd) ? true : false; + } + + void UIWindowsWnd::EnableWindow(bool enable) + { + ::EnableWindow(m_hWnd, enable); + } + + bool UIWindowsWnd::IsEnableWindow() + { + return ::IsWindowEnabled(m_hWnd) ? true : false; + } + + bool UIWindowsWnd::IsMinimize() + { + return IsIconic(m_hWnd); + } + + bool UIWindowsWnd::IsMaximize() + { + return IsZoomed(m_hWnd); + } + + void UIWindowsWnd::CenterWindow() + { + MONITORINFO monitor_info; + monitor_info.cbSize = sizeof(monitor_info); + GetMonitorInfoW(MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST), + &monitor_info); + RECT& rcMonitor = monitor_info.rcMonitor; + + RECT rcWindow { 0 }; + ::GetWindowRect(m_hWnd, &rcWindow); + + int pos[2] + { + int((rcMonitor.right - rcMonitor.left) - (rcWindow.right - rcWindow.left)) / 2, + int((rcMonitor.bottom - rcMonitor.top) - (rcWindow.bottom - rcWindow.top)) / 2 + }; + + SetWindowPos(m_hWnd, nullptr, rcMonitor.left + pos[0], rcMonitor.top + pos[1], -1, -1, + SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + } + + void UIWindowsWnd::CloseWindow() + { + ::PostMessageW(m_hWnd, WM_CLOSE, 0, 0); + } + + void UIWindowsWnd::SetWindowRect(UIRect rect) + { + //SetWindowPos(m_hWnd, nullptr, rect.left, rect.top, rect.GetWidth(), rect.GetHeight(), SWP_NOOWNERZORDER | SWP_NOACTIVATE) + ::MoveWindow(m_hWnd, rect.left, rect.top, rect.GetWidth(), rect.GetHeight(), TRUE); + } + + UIRect UIWindowsWnd::GetWindowRect(bool client) + { + RECT rect = { 0 }; + if (client) + ::GetClientRect(m_hWnd, &rect); + else + ::GetWindowRect(m_hWnd, &rect); + return UIRect(_m_rect(rect.left, rect.top, rect.right, rect.bottom)); + } + + void UIWindowsWnd::SetWindowAlpha(_m_byte alpha, bool draw) + { + m_alpha = alpha; + if(!m_layerWnd) + SetLayeredWindowAttributes(m_hWnd, RGB(0, 0, 0), alpha, 3UL); + } + + _m_byte UIWindowsWnd::GetWindowAlpha() + { + return m_alpha; + } + + _m_ptrv UIWindowsWnd::GetWindowHandle() + { + return (_m_ptrv)m_hWnd; + } + + void UIWindowsWnd::SetMinimSize(UISize min) + { + m_minSize = min; + } + + void UIWindowsWnd::SetVerticalSync(bool sync) + { + m_VSync = sync; + auto render = GetRender(); +#if MUI_CFG_ENABLE_OPENGL + if (std::wstring_view(render->GetRenderName()) == L"OpenGL") + { + render->RunTask([&] + { + render->GetBase()->VerticalSync(sync); + }); + } +#endif + } + + UISize UIWindowsWnd::GetWindowSrcSize() + { + return m_srcSize; + } + + void UIWindowsWnd::Present(MRenderCmd* render, const _m_rect_t* rcPaint) + { + std::wstring_view renderName = render->GetRenderName(); + //PAINTSTRUCT pt; + HDC hdc = GetDC(m_hWnd); //BeginPaint(m_hWnd, &pt); + if (renderName == L"GDIPlus" && hdc) + { + auto _render = render->GetBase(); + auto customCmd = [&_render, this, &hdc, &rcPaint]() + { + HDC pDC = (HDC)_render->GetDC(); + BLENDFUNCTION bf = { AC_SRC_OVER, 0, m_alpha, AC_SRC_ALPHA }; + + UIRect rect = GetWindowRect(true); + int WndWidth = rect.GetWidth(); + int WndHeight = rect.GetHeight(); + + //分层窗口更新 + if (m_layerWnd) + { + POINT ptDest = { rect.left, rect.top }; + POINT point = { 0,0 }; + SIZE pSize = { WndWidth, WndHeight }; + RECT pDirty = { rcPaint->left, rcPaint->top, rcPaint->right, rcPaint->bottom }; + + UPDATELAYEREDWINDOWINFO info; + info.cbSize = sizeof(UPDATELAYEREDWINDOWINFO); + info.crKey = 0; + info.dwFlags = ULW_ALPHA; + info.hdcDst = nullptr; + info.hdcSrc = pDC; + info.pblend = &bf; + info.pptDst = &ptDest; + info.pptSrc = &point; + info.prcDirty = &pDirty; + info.psize = &pSize; + UpdateLayeredWindowIndirect(m_hWnd, &info); + } + else + AlphaBlend(hdc, 0, 0, WndWidth, WndHeight, pDC, 0, 0, WndWidth, WndHeight, bf); + + _render->ReleaseDC(); + }; + render->RunTask(customCmd); + render->EndDraw(); + } + ReleaseDC(m_hWnd, hdc); + ValidateRect(m_hWnd, nullptr); + //EndPaint(m_hWnd, &pt); + } + + bool UIWindowsWnd::InitRender(MRenderCmd* render) + { + if (render) + { + UIRect rcWindow = GetWindowRect(); + + return render->InitRender((_m_uint)rcWindow.GetWidth(), (_m_uint)rcWindow.GetHeight()); + } + return false; + } + + MEventCodeEnum UIWindowsWnd::ConvertEventCode(_m_uint src) + { + switch (src) + { + case WM_MOVE: + return M_WND_MOVE; + case WM_SIZE: + return M_WND_SIZE; + case WM_SETFOCUS: + return M_WND_SETFOCUS; + case WM_KILLFOCUS: + return M_WND_KILLFOCUS; + case WM_PAINT: + return M_WND_PAINT; + case WM_CLOSE: + return M_WND_CLOSE; + case WM_KEYDOWN: + return M_WND_KEYDOWN; + case WM_KEYUP: + return M_WND_KEYUP; + case WM_COMMAND: + return M_WND_COMMAND; + case WM_SYSCOMMAND: + return M_WND_SYSCOMMAND; + case WM_TIMER: + return M_WND_TIMER; + case WM_MOUSEMOVE: + return M_MOUSE_MOVE; + case WM_MOUSEWHEEL: + return M_MOUSE_WHEEL; + case WM_MOUSEHOVER: + return M_MOUSE_HOVER; + case WM_MOUSELEAVE: + return M_MOUSE_LEAVE; + case WM_LBUTTONDOWN: + return M_MOUSE_LBDOWN; + case WM_LBUTTONUP: + return M_MOUSE_LBUP; + case WM_LBUTTONDBLCLK: + return M_MOUSE_LBDBCLICK; + case WM_RBUTTONDOWN: + return M_MOUSE_RBDOWN; + case WM_RBUTTONUP: + return M_MOUSE_RBUP; + case WM_RBUTTONDBLCLK: + return M_MOUSE_RBDBCLICK; + case WM_MBUTTONDOWN: + return M_MOUSE_MBDOWN; + case WM_MBUTTONUP: + return M_MOUSE_MBUP; + case WM_MBUTTONDBLCLK: + return M_MOUSE_MBDBCLICK; + case WM_SETCURSOR: + return M_SETCURSOR; + default: + break; + } + return MEventCodeEnum(src); + } + + _m_uint UIWindowsWnd::ConvertEventCode(MEventCodeEnum src) + { + switch (src) + { + case M_WND_MOVE: + return WM_MOVE; + case M_WND_SIZE: + return WM_SIZE; + case M_WND_SETFOCUS: + return WM_SETFOCUS; + case M_WND_KILLFOCUS: + return WM_KILLFOCUS; + case M_WND_PAINT: + return WM_PAINT; + case M_WND_CLOSE: + return WM_CLOSE; + case M_WND_KEYDOWN: + return WM_KEYDOWN; + case M_WND_KEYUP: + return WM_KEYUP; + case M_WND_COMMAND: + return WM_COMMAND; + case M_WND_SYSCOMMAND: + return WM_SYSCOMMAND; + case M_WND_TIMER: + return WM_TIMER; + case M_MOUSE_MOVE: + return WM_MOUSEMOVE; + case M_MOUSE_WHEEL: + return WM_MOUSEWHEEL; + case M_MOUSE_HOVER: + return WM_MOUSEHOVER; + case M_MOUSE_LEAVE: + return WM_MOUSELEAVE; + case M_MOUSE_LBDOWN: + return WM_LBUTTONDOWN; + case M_MOUSE_LBUP: + return WM_LBUTTONUP; + case M_MOUSE_LBDBCLICK: + return WM_LBUTTONDBLCLK; + case M_MOUSE_RBDOWN: + return WM_RBUTTONDOWN; + case M_MOUSE_RBUP: + return WM_RBUTTONUP; + case M_MOUSE_RBDBCLICK: + return WM_RBUTTONDBLCLK; + case M_MOUSE_MBDOWN: + return WM_MBUTTONDOWN; + case M_MOUSE_MBUP: + return WM_MBUTTONUP; + case M_MOUSE_MBDBCLICK: + return WM_MBUTTONDBLCLK; + case M_SETCURSOR: + return WM_SETCURSOR; + default: + break; + } + return static_cast<_m_uint>(src); + } + + + LRESULT UIWindowsWnd::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) + { + auto window = (UIWindowsWnd*)GetWindowLongPtrW(hWnd, GWLP_USERDATA); + auto param = std::make_pair((_m_param)wParam, (_m_param)lParam); + + if (message == WM_NCCREATE) + { + CREATESTRUCT* lpcs = (CREATESTRUCT*)lParam; + window = (UIWindowsWnd*)lpcs->lpCreateParams; + window->m_hWnd = hWnd; + SetWindowLongPtrW(hWnd, GWLP_USERDATA, (LPARAM)window); + } + else if (message == WM_DESTROY) + { + if (window) + { + LRESULT result = DefWindowProcW(hWnd, message, wParam, lParam); + SetWindowLongPtrW(window->m_hWnd, GWLP_USERDATA, 0L); + if (window->IsMainWindow()) + PostQuitMessage(0); + window->EventSource(window->ConvertEventCode(message), (_m_param)¶m); + return result; + } + } + + if (window) + { + if (message == WM_MOUSEMOVE) + window->m_cachePos = (_m_param)lParam; + //因为此消息附带的鼠标坐标为屏幕坐标 所以使用MOUSEMOVE的坐标 + else if (message == WM_MOUSEWHEEL) + param.second = window->m_cachePos; + else if(message == WM_GETMINMAXINFO) + { + auto pMinMaxInfo = (MINMAXINFO*)lParam; + + //设置窗口的最小宽度和最小高度 + auto scale = window->GetWindowScale(); + pMinMaxInfo->ptMinTrackSize.x = _scale_to(window->m_minSize.width, scale.cx); + pMinMaxInfo->ptMinTrackSize.y = _scale_to(window->m_minSize.height, scale.cy); + } + + if (window->m_notitleWnd) + { + switch (message) + { + //无标题栏标题栏 扩展窗口客户区 + case WM_NCCALCSIZE: + { + typedef void (WINAPI* PGetNTVer)(DWORD*, DWORD*, DWORD*); + static HMODULE hModule = GetModuleHandleW(L"ntdll.dll"); + static auto GetNTVer = (PGetNTVer)GetProcAddress(hModule, "RtlGetNtVersionNumbers"); + DWORD Major = 0; + GetNTVer(&Major, nullptr, nullptr); + + //win10以下版本删除边框 + if (wParam && Major < 10) + return 1; + + if (!lParam) + { + if (const auto ret = window->EventSource(window->ConvertEventCode(message), (_m_param)¶m)) + return ret; + return DefWindowProcW(hWnd, message, wParam, lParam); + } + + int frameX = GetThemeSysSize(NULL, SM_CXFRAME); + int frameY = GetThemeSysSize(NULL, SM_CYFRAME); + int padding = GetThemeSysSize(NULL, SM_CXPADDEDBORDER); + + NCCALCSIZE_PARAMS* params = (NCCALCSIZE_PARAMS*)lParam; + RECT* rgrc = params->rgrc; + + rgrc->right -= frameX + padding; + rgrc->left += frameX + padding; + rgrc->bottom -= frameY + padding; + + WINDOWPLACEMENT placement = { 0 }; + placement.length = sizeof(WINDOWPLACEMENT); + if (GetWindowPlacement(hWnd, &placement)) + { + if (placement.showCmd == SW_SHOWMAXIMIZED) + rgrc->top += padding; + } + + return true; + } + break; + //重新计算框架 扩展到整窗 + case WM_CREATE: + { + auto rect = window->GetWindowRect(); + SetWindowPos(hWnd, nullptr, rect.left, rect.top, + rect.GetWidth(), rect.GetHeight(), SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE); + } + break; + default: + break; + } + } + + if (const auto ret = window->EventSource(window->ConvertEventCode(message), (_m_param)¶m)) + return ret; + } + return DefWindowProcW(hWnd, message, wParam, lParam); + } + + BOOL UIWindowsWnd::RegisterWindowClass() + { + WNDCLASSEX wcx; + wcx.cbSize = sizeof(WNDCLASSEX); // size of structure + wcx.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; // redraw if size changes + wcx.lpfnWndProc = UIWindowsWnd::WndProc; // points to window procedure + wcx.cbClsExtra = 0; // no extra class memory + wcx.cbWndExtra = 0; // no extra window memory + wcx.hInstance = (HINSTANCE)&__ImageBase; // handle to instance + wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION); // predefined app. icon + wcx.hCursor = LoadCursor(NULL, IDC_ARROW); // predefined arrow + wcx.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // white background brush + wcx.lpszMenuName = NULL; // name of menu resource + wcx.lpszClassName = M_DEF_CLSNAME.data(); // name of window class + wcx.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // small class icon + + return RegisterClassExW(&wcx) != 0 || GetLastError() == ERROR_CLASS_ALREADY_EXISTS; + } +} +#endif // _WIN32 diff --git a/MiaoUILite.sln b/MiaoUILite.sln new file mode 100644 index 0000000..350ceac --- /dev/null +++ b/MiaoUILite.sln @@ -0,0 +1,57 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32602.215 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MiaoUILite", "MiaoUI\MiaoUI.vcxproj", "{2F7059F0-B4A9-4E52-A553-EB0667880680}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MiaoUITest", "MiaoUITest\MiaoUITest.vcxproj", "{37C86F34-69B2-4BFE-819A-D775E7DF3B41}" + ProjectSection(ProjectDependencies) = postProject + {2F7059F0-B4A9-4E52-A553-EB0667880680} = {2F7059F0-B4A9-4E52-A553-EB0667880680} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DmResEditor", "DmResEditor\DmResEditor.vcxproj", "{9D0D532D-C306-47CA-B438-88A0408831D1}" + ProjectSection(ProjectDependencies) = postProject + {2F7059F0-B4A9-4E52-A553-EB0667880680} = {2F7059F0-B4A9-4E52-A553-EB0667880680} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2F7059F0-B4A9-4E52-A553-EB0667880680}.Debug|x64.ActiveCfg = Debug|x64 + {2F7059F0-B4A9-4E52-A553-EB0667880680}.Debug|x64.Build.0 = Debug|x64 + {2F7059F0-B4A9-4E52-A553-EB0667880680}.Debug|x86.ActiveCfg = Debug|Win32 + {2F7059F0-B4A9-4E52-A553-EB0667880680}.Debug|x86.Build.0 = Debug|Win32 + {2F7059F0-B4A9-4E52-A553-EB0667880680}.Release|x64.ActiveCfg = Release|x64 + {2F7059F0-B4A9-4E52-A553-EB0667880680}.Release|x64.Build.0 = Release|x64 + {2F7059F0-B4A9-4E52-A553-EB0667880680}.Release|x86.ActiveCfg = Release|Win32 + {2F7059F0-B4A9-4E52-A553-EB0667880680}.Release|x86.Build.0 = Release|Win32 + {37C86F34-69B2-4BFE-819A-D775E7DF3B41}.Debug|x64.ActiveCfg = Debug|x64 + {37C86F34-69B2-4BFE-819A-D775E7DF3B41}.Debug|x64.Build.0 = Debug|x64 + {37C86F34-69B2-4BFE-819A-D775E7DF3B41}.Debug|x86.ActiveCfg = Debug|Win32 + {37C86F34-69B2-4BFE-819A-D775E7DF3B41}.Debug|x86.Build.0 = Debug|Win32 + {37C86F34-69B2-4BFE-819A-D775E7DF3B41}.Release|x64.ActiveCfg = Release|x64 + {37C86F34-69B2-4BFE-819A-D775E7DF3B41}.Release|x64.Build.0 = Release|x64 + {37C86F34-69B2-4BFE-819A-D775E7DF3B41}.Release|x86.ActiveCfg = Release|Win32 + {37C86F34-69B2-4BFE-819A-D775E7DF3B41}.Release|x86.Build.0 = Release|Win32 + {9D0D532D-C306-47CA-B438-88A0408831D1}.Debug|x64.ActiveCfg = Debug|x64 + {9D0D532D-C306-47CA-B438-88A0408831D1}.Debug|x64.Build.0 = Debug|x64 + {9D0D532D-C306-47CA-B438-88A0408831D1}.Debug|x86.ActiveCfg = Debug|Win32 + {9D0D532D-C306-47CA-B438-88A0408831D1}.Debug|x86.Build.0 = Debug|Win32 + {9D0D532D-C306-47CA-B438-88A0408831D1}.Release|x64.ActiveCfg = Release|x64 + {9D0D532D-C306-47CA-B438-88A0408831D1}.Release|x64.Build.0 = Release|x64 + {9D0D532D-C306-47CA-B438-88A0408831D1}.Release|x86.ActiveCfg = Release|Win32 + {9D0D532D-C306-47CA-B438-88A0408831D1}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4EF0D5CC-A681-4913-9705-8172C9A229B4} + EndGlobalSection +EndGlobal diff --git a/MiaoUITest/ControlTest.cpp b/MiaoUITest/ControlTest.cpp new file mode 100644 index 0000000..67082fc --- /dev/null +++ b/MiaoUITest/ControlTest.cpp @@ -0,0 +1,112 @@ +#include "ControlTest.h" +#include "MiaoUITest.h" + +namespace Mui::Ctrl +{ + + UITestCtrl::UITestCtrl(UIControl* parent) + { + M_ASSERT(parent) + parent->AddChildren(this); + m_cacheSupport = true; + } + + UITestCtrl::~UITestCtrl() + { + } + + void UITestCtrl::OnPaintProc(MPCPaintParam param) + { + if (!m_pen) + m_pen = param->render->CreatePen(10, Color::M_RGBA(255, 0, 0, 255)); + if(!m_staticBrush) + m_staticBrush = param->render->CreateBrush(Color::M_GREEN); + if (!m_bitmap) + m_bitmap = param->render->CreateBitmap(FS::MGetCurrentDir() + L"\\IMG_0065.PNG"); + + UIRect dst = UIRect(param->destRect->left, param->destRect->top, 200, 200); + + dst.Offset(10, 10); + + m_pen->SetWidthAndColor(1, Color::M_RED); + param->render->DrawLine({ dst.left, dst.top }, { dst.left + 100, dst.top + 100 }, m_pen); + + dst.ResetOffset(); + dst.Offset(200, 10); + + std::pair<_m_color, float> vertex[4]; + vertex[0] = std::make_pair(Color::M_White, 0.f); + vertex[1] = std::make_pair(Color::M_RED, 1.f); + + m_brush = param->render->CreateGradientBrush(vertex, 2, { 0, 100 }, { 200, 100 }); + + param->render->FillRectangle(dst, m_brush); + + vertex[0] = std::make_pair(0, 0.f); + vertex[1] = std::make_pair(Color::M_Black, 1.f); + + m_brush = param->render->CreateGradientBrush(vertex, 2, { 100, 0 }, { 100, 200 }); + + param->render->FillRectangle(dst, m_brush); + + dst.ResetOffset(); + dst.Offset(10, 240); + param->render->FillEllipse(dst, m_staticBrush); + + dst.ResetOffset(); + dst.Offset(440, 10); + UIRect geometryRc = dst; + if (param->cacheCanvas) + { + auto sub = UINodeBase::m_data.SubAtlas->GetSubRect(); + geometryRc.Offset(sub.left, sub.top); + } + m_geometry = param->render->CreateRoundGeometry(geometryRc, 20.f); + + param->render->PushClipGeometry(m_geometry); + + param->render->DrawBitmap(m_bitmap, 255, *param->destRect); + + //m_pen->SetWidthAndColor(1, Color::M_RGBA(255, 0, 0, 255)); + //render->DrawEllipse(dst_, m_pen); + //render->FillEllipse(dst_, m_brush); + param->render->PopClipGeometry(); + + dst.ResetOffset(); + dst.Offset(480, 240); + m_pen->SetWidthAndColor(4, Color::M_RGBA(0, 255, 0, 255)); + //param->render->DrawRoundedRect(dst, 50.f, m_pen); + param->render->FillRoundedRect(dst, 50.f, m_staticBrush); + + dst.ResetOffset(); + dst.Offset(240, 240); + geometryRc = dst; + if (param->cacheCanvas) + { + auto sub = UINodeBase::m_data.SubAtlas->GetSubRect(); + geometryRc.Offset(sub.left, sub.top); + } + + m_geometry = param->render->CreateEllipseGeometry(geometryRc); + + param->render->PushClipGeometry(m_geometry); + + param->render->DrawBitmap(m_bitmap, 255, *param->destRect); + + param->render->PopClipGeometry(); + + dst.ResetOffset(); + dst.Offset(720, 240); + m_pen->SetWidthAndColor(4, Color::M_BLUE); + param->render->DrawEllipse(dst, m_pen); + } + + void UITestCtrl::Register() + { + static auto method = [](UIControl* parent) { + return new UITestCtrl(parent); + }; + CtrlMgr::RegisterControl(ClassName, method); + } + +} diff --git a/MiaoUITest/ControlTest.h b/MiaoUITest/ControlTest.h new file mode 100644 index 0000000..476af8d --- /dev/null +++ b/MiaoUITest/ControlTest.h @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace Mui::Ctrl +{ + + class UITestCtrl : public UIControl + { + public: + UITestCtrl(UIControl* parent); + ~UITestCtrl() override; + + //控件类名和注册方法 + MCTRL_DEFNAME(L"UITestCtrl"); + + protected: + UITestCtrl() = default; + + void OnPaintProc(MPCPaintParam param) override; + + private: + MPenPtr m_pen = nullptr; + MGradientBrushPtr m_brush = nullptr; + MBrushPtr m_staticBrush = nullptr; + MBitmapPtr m_bitmap = nullptr; + MGeometryPtr m_geometry = nullptr; + }; +} diff --git a/MiaoUITest/MiaoUITest.cpp b/MiaoUITest/MiaoUITest.cpp new file mode 100644 index 0000000..cf8fe2c --- /dev/null +++ b/MiaoUITest/MiaoUITest.cpp @@ -0,0 +1,179 @@ +#include "MiaoUITest.h" +#include "ControlTest.h" +#include + +using namespace Mui; + +//窗口尺寸 +UISize g_wndSize = { 1226, 750 }; + +//应用程序入口点 +int APIENTRY wWinMain(_In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPWSTR lpCmdLine, + _In_ int nCmdShow) +{ + MiaoUI::Render renderType = MiaoUI::Render::Auto; + + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + M_SetLastExceptionNotify(); + + //初始化界面库 全局仅有一个MiaoUI类 + MiaoUI engine; + std::wstring err; + if (!engine.InitEngine(err, renderType)) + { + MessageBoxW(nullptr, (L"MiaoUI初始化失败! 错误信息: " + err).c_str(), L"error", MB_ICONERROR); + return 0; + } + + //注册自定义控件 + Ctrl::UITestCtrl::Register(); + + //创建窗口上下文 + std::wstring title = L"MiaoUI Demo " + std::wstring(Settings::MuiEngineVer) + L" - Test"; + MWindowCtx* windowCtx = engine.CreateWindowCtx(UIRect(0, 0, g_wndSize.width, g_wndSize.height), MiaoUI::MWindowType::Normal, title, true, true); + + //加载默认样式和外部资源 + auto mgr = windowCtx->Base()->GetResourceMgr(); + + mgr->AddResourcePath(FS::MGetCurrentDir() + L"\\resource.dmres", L"12345678"); + + windowCtx->XML()->LoadDefaultStyle(); + + //可以设置全局控件的默认属性 + std::wstring defList = MXMLCODE( + + ); + windowCtx->XML()->AddDefPropGroup(defList, true); + + //绑定窗口控件事件回调 + windowCtx->SetEventCallback(UI::EventProc); + //初始化窗口 + if (!windowCtx->InitWindow(UI::InitWindow, false)) + { + MessageBoxW(nullptr, L"初始化窗口失败!", L"error", MB_ICONERROR); + return 0; + } + + //可以访问窗口类设置更多 + auto base = windowCtx->Base(); + + base->CenterWindow(); + //base->ShowDebugRect(true); + //base->SetRenderMode(true); + + base->ShowWindow(true); + + //base->SetMaxFPSLimit(60); + + //窗口消息循环 直到窗口关闭 + windowCtx->EventLoop(); + return 0; +} + +namespace UI +{ + bool EventProc(MWindowCtx* ctx, UINotifyEvent event, Ctrl::UIControl* control, _m_param param) + { + if (MUIEVENT(Event_Mouse_LDown, L"player_pos")) + { + control->SetUserData(1); + } + else if (MUIEVENT(Event_Mouse_LUp, L"player_pos")) + { + control->SetUserData(0); + float pos = (float)static_cast(control)->GetCurValue() / 100.f; + auto cur = static_cast(control->GetParent()->FindChildren(L"player_cur")); + //m_player->SetTrackPlaybackPos(m_track, pos); + //m_videoPlayer->Seek(pos); + } + else if (MUIEVENT(Event_Mouse_LClick, L"player_pause")) + { + if (control->GetUserData() == 0) + { + control->SetUserData(1); + //m_player->PauseTrack(m_track); + //m_videoPlayer->Pause(); + static_cast(control)->SetAttribute(L"text", L"继续"); + } + else + { + control->SetUserData(0); + //m_player->PlayTrack(m_track); + //m_videoPlayer->Play(); + static_cast(control)->SetAttribute(L"text", L"暂停"); + } + } + else if (MUIEVENT(Event_Mouse_LClick, L"teststr")) + { + ctx->XML()->SetStringValue(L"teststr", L"已更新字符串"); + } + else if (MUIEVENT(Event_Slider_Change, L"hsv")) + { + auto color = ctx->Base()->GetRootControl()->Child(L"color"); + auto hsv = color->GetHSVColor(); + hsv.hue = (_m_ushort)param; + color->SetHSVColor(hsv); + } + return false; + } + + bool InitWindow(MWindowCtx* ctx, Ctrl::UIControl* root, XML::MuiXML* xmlUI) + { + using namespace Ctrl; + std::wstring xml = MXMLCODE( + + + + + + + ); + + //xmlUI->AddStringList(L"teststr", L"测试字符串"); + + if (!xmlUI->CreateUIFromXML(root, xml)) + { + MessageBoxW(nullptr, L"XML代码有误!创建UI失败!", L"error", MB_ICONERROR); + return false; + } + + //UIComBox* com = root->Child(L"testcom"); + //UIListBox* list = root->Child(L"testlist"); + //for (size_t i = 0; i < 100; i++) + //{ + // ListItem* item = new ListItem(); + // item->SetText(L"测试列表项" + std::to_wstring(i + 1)); + // com->AddItem(item, -1, false); + // item = new ListItem(); + // item->SetText(L"测试列表项" + std::to_wstring(i + 1)); + // list->AddItem(item, -1, false); + //} + + //UINavBar* navbar = root->Child(L"navbar"); + //for (int i = 0; i < 5; i++) + //{ + // std::wstring title = L"导航项目" + std::to_wstring(i + 1); + // navbar->AddItem(title, -1, false); + //} + + ////设置一个计时器 每1秒更新一次fps + //UILabel* label = root->Child(L"fps"); + //root->GetParentWin()->SetTimer(1000, [=](_m_ptrv window, _m_param, _m_ulong) + //{ + // auto _wnd = reinterpret_cast(window); + // auto fps = _wnd->GetLastFPS(); + // std::wstring info = L"FPS: " + std::to_wstring(fps); + // info += L"\n即时渲染: "; + // if (_wnd->GetRenderMode()) + // info += L"true"; + // else + // info += L"false"; + // label->SetAttribute(L"text", info); + //}); + + return true; + } +} \ No newline at end of file diff --git a/MiaoUITest/MiaoUITest.h b/MiaoUITest/MiaoUITest.h new file mode 100644 index 0000000..67fbd83 --- /dev/null +++ b/MiaoUITest/MiaoUITest.h @@ -0,0 +1,12 @@ +#pragma once +#include +#include +#include +#include + +//窗口事件回调 +namespace UI +{ + extern bool EventProc(Mui::MWindowCtx*, Mui::UINotifyEvent, Mui::Ctrl::UIControl*, Mui::_m_param); + extern bool InitWindow(Mui::MWindowCtx*, Mui::Ctrl::UIControl*, Mui::XML::MuiXML*); +} \ No newline at end of file diff --git a/MiaoUITest/MiaoUITest.ico b/MiaoUITest/MiaoUITest.ico new file mode 100644 index 0000000..886ed00 Binary files /dev/null and b/MiaoUITest/MiaoUITest.ico differ diff --git a/MiaoUITest/MiaoUITest.vcxproj b/MiaoUITest/MiaoUITest.vcxproj new file mode 100644 index 0000000..8cf68e8 --- /dev/null +++ b/MiaoUITest/MiaoUITest.vcxproj @@ -0,0 +1,217 @@ + + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {37c86f34-69b2-4bfe-819a-d775e7df3b41} + MiaoUITest + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)bin\Test\Debug\ + $(SolutionDir)MiaoUITest\Cache\Debug\ + + + false + $(SolutionDir)bin\Test\Release\ + $(SolutionDir)MiaoUITest\Cache\Release\ + + + true + $(SolutionDir)bin\Test\Debug64\ + $(SolutionDir)MiaoUITest\Cache\Debug64\ + + + false + $(SolutionDir)bin\Test\Release64\ + $(SolutionDir)MiaoUITest\Cache\Release64\ + + + + Level3 + true + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreadedDebug + $(SolutionDir)\MiaoUI\src\include;$(SolutionDir)ThirdParty\ffmpeg-4.2.1\include + stdcpp17 + + + Windows + true + true + $(SolutionDir)MiaoUI\library\;$(SolutionDir)MiaoUITest\library\ + + + PerMonitorHighDPIAware + + + + + Level3 + true + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + $(SolutionDir)\MiaoUI\src\include;$(SolutionDir)ThirdParty\ffmpeg-4.2.1\include + stdcpp17 + + + Windows + true + true + true + $(SolutionDir)MiaoUI\library\;$(SolutionDir)MiaoUITest\library\ + + + PerMonitorHighDPIAware + + + + + Level3 + true + _DEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebug + $(SolutionDir)\MiaoUI\src\include;$(SolutionDir)ThirdParty\ffmpeg-4.2.1\include + stdcpp17 + + + Windows + true + true + $(SolutionDir)MiaoUI\library\ + %(AdditionalDependencies) + + + false + + + + + Level3 + true + true + true + NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + $(SolutionDir)\MiaoUI\src\include;$(SolutionDir)ThirdParty\ffmpeg-4.2.1\include + stdcpp17 + + + Windows + true + true + true + $(SolutionDir)MiaoUI\library\ + libcmtd.lib + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 + + + + + + \ No newline at end of file diff --git a/MiaoUITest/MiaoUITest.vcxproj.filters b/MiaoUITest/MiaoUITest.vcxproj.filters new file mode 100644 index 0000000..d3a17b7 --- /dev/null +++ b/MiaoUITest/MiaoUITest.vcxproj.filters @@ -0,0 +1,55 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + + + 资源文件 + + + 资源文件 + + + 资源文件 + + + + + 资源文件 + + + + + + \ No newline at end of file diff --git a/MiaoUITest/MiaoUITest.vcxproj.user b/MiaoUITest/MiaoUITest.vcxproj.user new file mode 100644 index 0000000..4af99c1 --- /dev/null +++ b/MiaoUITest/MiaoUITest.vcxproj.user @@ -0,0 +1,23 @@ + + + + WindowsLocalDebugger + + + + + + + RemoteWithAuthentication + + + + + + + RemoteWithAuthentication + WindowsLocalDebugger + + + + \ No newline at end of file diff --git a/MiaoUITest/Resource.aps b/MiaoUITest/Resource.aps new file mode 100644 index 0000000..1114270 Binary files /dev/null and b/MiaoUITest/Resource.aps differ diff --git a/MiaoUITest/Resource.rc b/MiaoUITest/Resource.rc new file mode 100644 index 0000000..cb718b4 --- /dev/null +++ b/MiaoUITest/Resource.rc @@ -0,0 +1,71 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource1.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// (壬й) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS) +LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED +#pragma code_page(936) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource1.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "MiaoUITest.ico" + +#endif // (壬й) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/MiaoUITest/icon.png b/MiaoUITest/icon.png new file mode 100644 index 0000000..f795fc9 Binary files /dev/null and b/MiaoUITest/icon.png differ diff --git a/MiaoUITest/packages.config b/MiaoUITest/packages.config new file mode 100644 index 0000000..43e1ed2 --- /dev/null +++ b/MiaoUITest/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/MiaoUITest/resource1.h b/MiaoUITest/resource1.h new file mode 100644 index 0000000..308fff4 --- /dev/null +++ b/MiaoUITest/resource1.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ 生成的包含文件。 +// 供 Resource.rc 使用 +// +#define IDI_ICON1 106 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 116 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/MiaoUITest/small.ico b/MiaoUITest/small.ico new file mode 100644 index 0000000..b3ec03b Binary files /dev/null and b/MiaoUITest/small.ico differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3de88f7 --- /dev/null +++ b/README.md @@ -0,0 +1,249 @@ +# MiaoUI library +MiaoUI是一套轻量级高性能的跨平台DirectUI渲染引擎库 并用于我们的一些内部项目 +> MiaoUI Core 仅用于内部项目 如果你具有访问权限请转到[MiaoUI](https://github.com/Maplespe/MiaoUI)仓库 本页面仅为面向开源项目的MiaoUI Lite页面 +## MiaoUI Lite +MiaoUI Lite 是 MiaoUI Core 的精简版 去除了大部分小型项目不常用的功能 并完全开放源代码 可以为小型项目提供快速美观的配置GUI 本项目也可作为一个起始框架来自定义改造 + +> 部分功能需要在`Mui_Config.h`中启用对应的宏定义 + +### 功能差异 +#### XML语法 +MiaoUI使用XML作为中间语言来创建UI +例如 +``` xml + + + + + + +``` +![screenshot](./screenshot/01.png) + +> Lite版本完全兼容Core版本的XML控件语法 Core的代码可以直接迁移至Lite 反之亦然 但请注意版本对应 Lite版本从2.0.8开始 兼容Core 2.1.0的语法 + + 完整的控件属性列表详见`AttribName.txt`和`GeometryXML.txt`文件 + + 或者直接查看对应控件的头文件 例如 `Mui_Button.h` 所有控件的属性列表都在头文件有注释 + +# + +> 若要从Core版本迁移到Lite版本 部分功能Lite不支持 以下列表列出具体项目 + +
+平台支持 + +| 平台支持 | Core v2.1.0 | Lite v2.0.8 | +| --- | :---: | :---: | +| Windows | 7+ | XP+ | +| Android | 7+ | +| Linux | X11 GUI | +
+ +
+用户层API + +| 控件 | Core v2.1.0 | Lite v2.0.8 | +| --- | :---: | :---: | +| UIButton | ✔ | ✔ | +| UICheckBox | ✔ | ✔ | +| UIColorPicker | ✔ | ✔ | +| UIComBox | ✔ | ✔ | +| UIControl | ✔ | ✔ | +| UIEditBox | ✔ | ✔ | +| UIEffectLayer | ✔ | +| UIGroup | ✔ | +| UIIconBtn | ✔ | +| UIImgBox | ✔ | ✔ | +| UIImgListBox | ✔ | +| UILabel | ✔ | ✔ | +| UIListBox | ✔ | ✔ | +| UIMenu | ✔ | +| UINavBar | ✔ | Basic | +| UIPanel | ✔ | ✔ | +| UIProgress | ✔ | ✔ | +| UIScroll | ✔ | ✔ | +| UISlider | ✔ | ✔ | +| UISwitch | ✔ | +| UIVideoPlayer | ✔ | +| UIPage | ✔ | +| UIDialogPage | ✔ | +| UITabPage | ✔ | + +| 资源类 | Core v2.1.0 | Lite v2.0.8 | +| --- | :---: | :---: | +| DMResFile V1 | ✔ | ✔ | +| DMResFile V2 | ✔ | + +| 其他功能 | Core v2.1.0 | Lite v2.0.8 | +| --- | :---: | :---: | +| SVG图像渲染 | ✔ | +| 缓动和动画效果 | ✔ | +| GPU硬件加速 | ✔ | +| 内存字体加载器 | ✔ | +| 滤镜效果 | ✔ | +| 帧缓存和批渲染 | ✔ | +| EditBox硬件加速 | ✔ | +| 亮/暗模式默认样式 | ✔ | 仅亮模式 | +| D3D设备丢失资源重建支持 | ✔ | +| UIBasicWindow脏区域支持 | | ✔ | +| 高DPI缩放支持 | ✔ | ✔ | + +
+ +
+底层API + +| 图形后端 | 适用平台 | Core v2.1.0 | Lite v2.0.8 | +| --- | --- | :---: | :---: | +| GDIPlus 1.0 | Windows | | ✔ +| Direct2D 1.1/D3D11 | Windows | ✔ | +| OpenGL Core 3.3 | Windows/Linux | ✔ | +| OpenGL ES 3.2 | Android | ✔ | + +> GDIPlus渲染器不支持以下接口 +> ##### Render: +> - CreateSVGBitmap +> - CreateSVGBitmapFromXML +> - DrawBatchBitmap +> - DrawBitmapEffects +> ##### Font: +> - SetFontSize的range参数 +> - SetFontStyle的range参数 +> - SetFontColor的range参数 + +| 音频后端 | 适用平台 | Core v2.1.0 | Lite v2.0.8 | +| --- | --- | :---: | :---: | +| DirectSound | Windows | ✔ +| OpenSL ES | Windows | ✔ | +| PulseAudio | Linux | + +| 解码器 | Core v2.1.0 | Lite v2.0.8 | +| --- | :---: | :---: | +| wave | ✔ | +| mp3 | ✔ | +| ogg | ✔ | +| ffmpeg(可选) | ✔ | +
+ +### 快速开始 +#### 先决条件 +- Visual Studio 2022 +- C++ MSVC v143生成工具 +- Windows 10/11 SDK +> Lite版本仅支持Windows 使用Visual Studio进行开发 +> 如果要支持Windows XP 你需要改用MSVC v141 WindowsXP工具集 +> MiaoUI基于C++17标准编写 + +你可以转到[Release页面](https://github.com/Maplespe/MiaoUILite/releases)下载已经编译好的库 或者自行编译 + +**如果要自行编译 请继续阅读以下内容** + +克隆本储存库 然后打开`MiaoUI.sln` +此解决方案分为3个项目 按需进行编译 +1. MiaoUILite - 项目核心库 +2. MiaoUITest - 用于测试的一个项目 可以测试库是否正常运行 +3. DmResEditor - DMResFile V1的文件编辑器 如果你打算使用这个文件系统 可以用它编辑dm资源文件 + +在编译之前 你可以修改MiaoUILite项目下的`Mui_Config.h`文件来控制一些功能的开关 + +生成并运行MiaoUITest项目这会自动编译MiaoUILite库 生成后的库文件位于`.\MiaoUI\library`目录下 + +**注意 项目默认是静态链接的 Release:/MT Debug:/MTD** +如果你想使用运行库 可以修改编译选项 + +#### 开始使用 +
+一个简单的demo + +创建一个新的C++项目 并设置好附加库目录到MiaoUI的`library`文件夹 +添加MiaoUI的`src\include`目录到附加头文件目录 +**无需向编译器添加附加库文件名 mui头文件已经添加了链接指令** + +项目必须基于C++17及以上 +```cpp +#include "Mui.h" + +using namespace Mui; + +int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) +{ + //注意 MiaoUI支持每个显示器的高DPI缩放 你可以在清单设置 或使用代码告诉系统 DPI由我们自己管理 + //Windows 8+才支持此API 低版本系统无需设置 + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + //设置MiaoUI默认的异常最后处理函数 你也可以自己设置回调 空留为默认 + M_SetLastExceptionNotify(); + + //初始化界面库 全局仅有一个MiaoUI类 + MiaoUI engine; + std::wstring err; + if (!engine.InitEngine(err)) + { + MessageBoxW(nullptr, (L"MiaoUI初始化失败! 错误信息: " + err).c_str(), L"error", MB_ICONERROR); + return 0; + } + + //创建窗口上下文 + std::wstring title = L"MiaoUI Demo " + std::wstring(Settings::MuiEngineVer) + L" - Test"; + MWindowCtx* windowCtx = engine.CreateWindowCtx(UIRect(0, 0, 452, 328), MiaoUI::MWindowType::Normal, title, true, true); + + //加载默认的UI样式 + windowCtx->XML()->LoadDefaultStyle(); + + //绑定全局控件事件回调 + windowCtx->SetEventCallback([](MWindowCtx* ctx, UINotifyEvent event, Ctrl::UIControl* control, _m_param param) + { + //你可以通过全局事件回调和MUIEVENT宏来处理控件事件 + //也可以通过绑定对应控件的EventSlot来单独处理 + if (MUIEVENT(Event_Mouse_LDown, L"test_button")) + { + MessageBoxW(ctx->Base()->GetWindowHandle(), L"hello world!", L"msg", MB_OK); + return true; + } + return false; + }); + + //初始化窗口 + bool result = windowCtx->InitWindow([](MWindowCtx* ctx, Ctrl::UIControl* root, XML::MuiXML* xmlUI) + { + //可以通过MXMLCODE宏内联XML代码 + std::wstring_view xml = MXMLCODE( + + + + + + + ); + + return xmlUI->CreateUIFromXML(root, xml.data()); + }, false); + if(!result) + { + MessageBoxW(nullptr, L"初始化窗口失败!XML代码不正确", L"error", MB_ICONERROR); + return 0; + } + + //可以访问窗口类设置更多 + auto base = windowCtx->Base(); + + base->CenterWindow(); + + //base->ShowDebugRect(true); //显示控件边界区域 用于调试 + //base->SetRenderMode(true); //启用即时渲染模式 可用于连续画面或者压力测试 + + base->ShowWindow(true); + + //base->SetMaxFPSLimit(60); //限制渲染的最大帧率 + + //窗口消息循环 直到窗口关闭 + windowCtx->EventLoop(); + return 0; +} +``` +
+ +### 依赖 +* [pugixml](https://github.com/zeux/pugixml) +* [VC_LTL](https://github.com/Chuyu-Team/VC-LTL5) \ No newline at end of file diff --git a/bin/Test/Debug64/IMG_0065.PNG b/bin/Test/Debug64/IMG_0065.PNG new file mode 100644 index 0000000..86a85b4 Binary files /dev/null and b/bin/Test/Debug64/IMG_0065.PNG differ diff --git a/bin/Test/Debug64/resource.dmres b/bin/Test/Debug64/resource.dmres new file mode 100644 index 0000000..dc2c5a2 Binary files /dev/null and b/bin/Test/Debug64/resource.dmres differ diff --git a/screenshot/01.png b/screenshot/01.png new file mode 100644 index 0000000..9945008 Binary files /dev/null and b/screenshot/01.png differ