commit ef5699db9efaea940eccc94441e51ab89704fa39 Author: Tracy Rust Date: Sun Dec 10 22:37:17 2023 -0500 Relicensing the code and removing it from github. Last commit was April 2021 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ba6341a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,219 @@ +#This file is part of the IVD project and is licensed under LGPL-3.0-only + +cmake_minimum_required(VERSION 3.12) + +find_program(CCACHE_PROGRAM ccache) +if(CCACHE_PROGRAM) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") +endif() + + +project(IVD CXX) +set(CMAKE_CXX_STANDARD 17) + +list(APPEND ivd_core_sources +src/runtimeattribute.cpp +src/runtimeattribute.h +src/attributepositionpair.h +src/runtimeattributeset.cpp +src/runtimeattributeset.h +src/canvas.h +src/color.cpp +src/color.h +src/compiler.cpp +src/compiler.h +src/defaults.cpp +src/defaults.h +src/displayitem.cpp +src/displayitem.h +src/driver.h +src/element.h +src/environment.cpp +src/environment.h +src/expression.cpp +src/expression.h +src/geometry.h +src/geometryproposal.h +src/keywords.h +src/standardstatekeys.h +src/statekey.h +src/statemanager.h +src/statemanager.cpp +src/states.h +src/text.cpp +src/text.h +src/valuekey.cpp +src/valuekey.h +src/corefonts.h +src/graph.cpp +src/graph.h +src/codeposition.h +src/virtualstatekey.h +src/virtualstatekey.cpp +src/binaryexpressionprinter.h +src/cbindings.cpp +src/referenceattribute.h +src/referenceattribute.cpp +src/referenceattributeset.h +src/referenceattributeset.cpp +src/attributebodytypes.h + + +src/user_include/IVD_c.h +src/user_include/IVD_status.h +src/user_include/IVD_constants_c.h +src/user_include/cpp/IVD_cpp.h +src/user_include/cpp/IVD_geometry.h +src/user_include/cpp/IVD_geometry_proposal.h + +src/widget.h + +src/shaping/line.h + +src/widgets/boxlayout.h +src/widgets/boxlayout.cpp +src/widgets/stacklayout.h +src/widgets/stacklayout.cpp +src/widgets/image.h +src/widgets/image.cpp + +src/specific_driver_sdl/cairocanvas.cpp +src/specific_driver_sdl/cairocanvas.h +src/specific_driver_sdl/sdldriver.cpp +src/specific_driver_sdl/sdldriver.h +src/specific_driver_sdl/sdlwindow.cpp +src/specific_driver_sdl/sdlwindow.h +src/specific_driver_sdl/textdriver.cpp + +${CMAKE_CURRENT_BINARY_DIR}/corefontsansbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontsansbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontsansboldbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontsansitalicbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontsansbolditalicbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontserifbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontserifboldbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontserifitalicbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontserifbolditalicbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontmonobinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontmonoboldbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontmonoitalicbinary.cpp +${CMAKE_CURRENT_BINARY_DIR}/corefontmonobolditalicbinary.cpp + +contrib/rustutils/lexcompare.h +contrib/rustutils/routine.h +contrib/rustutils/easyuniquepointer.h +) + +find_package(OpenImageIO REQUIRED) +find_package(reprodyne 1.0.0 REQUIRED) +find_package(Freetype REQUIRED) +find_package(Catch2 REQUIRED) +find_package(SDL2 REQUIRED) + +SET(IVD_COMMON_LIBS + SDL2 + cairo + harfbuzz + freetype + OpenImageIO) + + +SET(IVD_INTERNAL_INCLUDES + src + contrib + ${REPRODYNE_INCLUDE_DIRS} + ${FREETYPE_INCLUDE_DIRS} + ${OPENIMAGEIO_INCLUDES}) + +add_library(ivd SHARED ${ivd_core_sources}) +target_include_directories(ivd PRIVATE PRIVATE ${IVD_INTERNAL_INCLUDES}) +target_link_libraries(ivd PRIVATE ${IVD_COMMON_LIBS}) + +set_target_properties(ivd PROPERTIES PUBLIC_HEADER + "${PROJECT_SOURCE_DIR}/src/user_include/cpp/IVD_geometry_proposal.h;${PROJECT_SOURCE_DIR}/src/user_include/IVD_c.h;${PROJECT_SOURCE_DIR}/src/user_include/cpp/IVD_geometry.h;${PROJECT_SOURCE_DIR}/src/user_include/IVD_constants.h;${PROJECT_SOURCE_DIR}/src/user_include/cpp/IVD_cpp.h;${PROJECT_SOURCE_DIR}/src/user_include/IVD_status.h") + + +add_executable(ivdRepro ${ivd_core_sources} src/tests/recordplayback.cpp) +target_include_directories(ivdRepro PRIVATE ${IVD_INTERNAL_INCLUDES}) +target_link_libraries(ivdRepro PRIVATE ${IVD_COMMON_LIBS} reprodyne) +target_compile_definitions(ivdRepro PUBLIC REPRODYNE_AVAILABLE) + + + +find_package(Python3 REQUIRED) +function(addFont sourcettf constantsymbol) + set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${constantsymbol}binary.cpp) + add_custom_command(DEPENDS ${sourcettf} binarysourcegenerator.py + COMMAND ${Python3_EXECUTABLE} binarysourcegenerator.py + ${sourcettf} corefonts.h ${constantsymbol} ${outfile} + OUTPUT ${outfile} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endfunction() + +addFont("contrib/fonts/LiberationSans-Regular.ttf" "corefontsans") +addFont("contrib/fonts/LiberationSans-Bold.ttf" "corefontsansbold") +addFont("contrib/fonts/LiberationSans-Italic.ttf" "corefontsansitalic") +addFont("contrib/fonts/LiberationSans-BoldItalic.ttf" "corefontsansbolditalic") + +addFont("contrib/fonts/LiberationSerif-Regular.ttf" "corefontserif") +addFont("contrib/fonts/LiberationSerif-Bold.ttf" "corefontserifbold") +addFont("contrib/fonts/LiberationSerif-Italic.ttf" "corefontserifitalic") +addFont("contrib/fonts/LiberationSerif-BoldItalic.ttf" "corefontserifbolditalic") + +addFont("contrib/fonts/LiberationMono-Regular.ttf" "corefontmono") +addFont("contrib/fonts/LiberationMono-Bold.ttf" "corefontmonobold") +addFont("contrib/fonts/LiberationMono-Italic.ttf" "corefontmonoitalic") +addFont("contrib/fonts/LiberationMono-BoldItalic.ttf" "corefontmonobolditalic") + +configure_file("contrib/test-images/jpeg_test_article.jpg" "jpeg_test_article.jpg" COPYONLY) +configure_file("contrib/test-images/png_test_article.png" "png_test_article.png" COPYONLY) + +add_executable(ivdruntime src/tests/lightruntime.cpp) +target_include_directories(ivdruntime PRIVATE src) +target_link_libraries(ivdruntime PRIVATE ivd) + +add_executable(ivdserializingcompiler src/tests/serializingcompiler.cpp) +target_include_directories(ivdserializingcompiler PRIVATE src contrib) +target_link_libraries(ivdserializingcompiler PRIVATE ivd) + + + +include(TestBigEndian) +TEST_BIG_ENDIAN(systemIsBigEndian) +if(systemIsBigEndian) + add_compile_definitions(BIG_ENDIAN_SYSTEM) +else() + add_compile_definitions(LITTLE_ENDIAN_SYSTEM) +endif() + +set(IVD_MAJOR_VERSION 0) +set(IVD_MINOR_VERSION 1) +set(IVD_PATCH_VERSION 0) + +set(IVD_API_VERSION ${IVD_MAJOR_VERSION}.${IVD_MINOR_VERSION}) +set(IVD_VERSION ${IVD_API_VERSION}.${IVD_PATCH_VERSION}) + +set(IVD_DEST_NAME IVD-${IVD_API_VERSION}) + +set(IVD_USER_INCLUDE_DIRS ${CMAKE_INSTALL_PREFIX}/include/${IVD_DEST_NAME}) + +install(TARGETS ivd EXPORT ivd-targets + ARCHIVE + DESTINATION lib/${IVD_DEST_NAME} + PUBLIC_HEADER + DESTINATION ${IVD_USER_INCLUDE_DIRS}) + +include(CMakePackageConfigHelpers) + +write_basic_package_version_file(ivd-config-version.cmake + VERSION ${IVD_VERSION} + COMPATIBILITY SameMajorVersion) + +configure_file(ivd-config.cmake.in + ${CMAKE_BINARY_DIR}/ivd-config.cmake @ONLY) + +install(FILES ${CMAKE_BINARY_DIR}/ivd-config.cmake ${CMAKE_BINARY_DIR}/ivd-config-version.cmake + DESTINATION lib/cmake/${IVD_DEST_NAME}) + +install(EXPORT ivd-targets + DESTINATION lib/cmake/${IVD_DEST_NAME}) diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE.txt @@ -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 +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f071cc --- /dev/null +++ b/README.md @@ -0,0 +1,500 @@ +# IVD - Interactive Visual Design Language + +IVD is a declarative GUI programming language and framework implementation. The goal is to make it so that you can describe *how* a interface should work and leave the rest to the computer, facilitating highly dynamic GUI programming, whilst maintaining maintainability. + +# Why? + +The decision to create IVD was not reached lightly. I knew it would be a huge undertaking (Although I underestimated how huge...), and spent considerable time trying to talk myself out of doing it. + +Aside from looking at all the GUI toolkits I could find before I broke ground, I had experienced developing user interfaces in the classical way (Qt/GTK), the "modern" way (HTML/CSS/JavaScript), and played around a bit with QML (which inspired some aspects of this project). All of these had their pros and cons, but none of them seemed to represent a true advancement in the problem space, and I'm arrogant enough that I thought I could maybe move the needle myself. + +# A Quick(ish) and Mostly Incomplete Rundown of IVD + +## IVD is Not Ready + +IVD is still in heavy development and as such this readme should be treated as a writeup of the project, rather than as a guide on using it. It's okay if you don't get the syntax completely, it's not written so that you are supposed to; just read on and the high level ought to stand out at least. + +## IVD is *Not* CSS++ +Before we get started, it's important to acknowledge that although the syntax is superficially similar, the theory of IVD is *very* different from the theory of CSS. Elements in IVD aren't bound to models by default, and they don't inherit attributes from their "parents" (Because they don't have static parents). About the only concepts that carry over from CSS are some attribute names (Although I've renamed some that are otherwise equivalent, just to piss you off), and the box model for styling. Most everything else is either taken more from traditional GUI toolkits or is novel. + +IVD allows for visual elements to be free and not bound to the model, so that your data model isn't corrupted with irrelevant noise which only exists for the presentation, as is always the case with `
` tags in any reasonably complex webpage. As such, it allows your data to be truly semantic. + +CSS is for *styling* models, and requires something like HTML and JavaScript to create a GUI with it. IVD on the other hand is for *defining* complete user interfaces with [*style*](https://i.redd.it/vq2q5dqh16qy.png). + +## Events Kinda Suck, State System to The Rescue +One great problem I think is the low level nature of typical GUI events. Take for example the case of an element needing to style itself differently when hovered in a scroll area. A naive approach involves monitoring "mouse motion in" and "mouse motion out" events, and setting your internal hover state accordingly. This is all fine and good until you have the cursor hovering over an element, and without moving the mouse, the user scrolls, suddenly throwing the hover out of sync. + +It's easy enough to fix said issue, but even easier to break again because the solution isn't intuitive. It's fun to watch applications gain and lose this bug as they go through updates. The idea is to leave these tricky edge cases to the GUI framework, instead of fixing and breaking (solved) problems like this all the time while in the middle of trying to fix something completely unrelated. + +The problem with individual widgets processing events is that they have no context (At least at the level that your typical GUI events exist, it is certainly possible to have higher level events, but IVD as a language has other benefits as well). In IVD, the environment is responsible for determining whether an item is hovered or not (Which was in part inspired by CSS's pseudo-classes): + + #element-name + { + color: red; + + state this.IVD-Item-Hovered: + color: blue; + + state ::.IVD-Mouse-Clicked: + color: green; + + state ::.IVD-Mouse-Clicked & this.IVD-Item-Hovered: + color: purple; + } + +Unlike CSS's pseudo-classes, IVD has a very powerful state system and allows boolean comparisons on states: + + state this.aState & (anotherState | !stateeee): + +"Overlapping" attributes, where two active states define a different value for a given attribute, override in descending order: + + # //anonymous element because you shouldn't be forced to name things you don't want to + { + attr: one; + + state sky-is-blue: + attr: two; + + state grass-is-green: + attr: three; //"three" is chosen by the runtime + } + + +Elements can manipulate states, even in other elements: + + #an-element + { + state coordinatedState: + color: blue; + } + + # + { + state aState: + induce-state: an-element.coordinatedState; + + //Can also toggle, unset, etc + toggle-state: state; + unset-state: state; + } + +Event-like behavior is handled by "trigger-states". These are states that are guaranteed by the runtime to last only a single frame. This allows you to set processes external to IVD in motion using the `trigger` attribute: + + # + { + state this.clicked: + trigger: IVD-Core-Quit; + } + + +Say you have a group of states, and only one can be active at any given time. It could be tabs, or a radio-box selection, etc. IVD can manage the exclusivity for you: + + # + { + radio-state: one, two, three; + + state one: + state two: + state three: + } + +And then only the last state to be set will be active in that element at any given time. + + + +## Positioning + +Widget hierarchies tend to be very ridged. In traditional GUI toolkits, this is because each widget "owns" it's children, and reparenting is an arduous task. HTML isn't much better, where even if you use JavaScript to restructure the DOM, it still has a specific default defined in a very different way from how your dynamic structure is defined. + +This makes it difficult to create GUIs that are easy to re-arrange. + +The two points that make IVD different here are that elements are completely symmetric, and the fact that the visual element's structure isn't tightly bound to a model. When in use, a model's hierarchy only applies locally, and is largely optional (The element hierarchy doesn't necessarily have to map 1:1 to the model). [More on models below](#models). + +IVD's structure being symmetric just means that positioning is always conditional. An element must choose to position itself within another element. It always starts out flat: + + # + { + state some-condition: + position-within: window; + + state some-other-condition: + position-within: another-element; + } + + +## Layouts/Materials + +One place where traditional toolkits beat HTML/CSS I think unequivically, is in their layout system. Floats... Are unnecessarily complicated to reason about, and enough tears have been shed over that system that I feel I need not ever speak of it again. + +IVD follows a typical nested layout system. Built in layouts include hbox and vbox, for horizontal and vertical rows respectively, and the stack layout for layering. + +Given the following example: + + #window + { + position-within: Environment; //Give us a window + layout: hbox; + } + + # + { + position-within: window; + text: "Aye"; + } + + # + { + position-within: window; + text: "Bye"; + } + + # + { + position-within: window; + text: "Cye"; + } + +The following is produced (please excuse the crudity of this model, I didn't have time to build it to scale or paint it): + + [AyeByeCye] + +That's great, but then the order is almost arbitrary (It's actually based on the order of element declaration but bear with me). To reserve specific "slots", we have a feature called "named-cells": + + #window + { + position-within: Environment; //Give us a window + layout: hbox; + + named-cells: first, middle, last; + } + + # + { + position-within: window.last; + text: "Cye"; + } + + # + { + position-within: window.first; + text: "Aye"; + } + + # + { + position-within: window.middle; + text: "Bye"; + } + +Which produces the same output: + + [AyeByeCye] + +This makes it painless to slip a column in between two elements later in the development cycle, without touching anything that already works. Empty cells are simply ignored, so they're great to declare where a notification or context popout should appear when it feels like it. + +Of course, it also makes it trivial to rearrange items: + + #window + { + named-cells: first, middle, last; + + state a-state: + named-cells: middle, first, last; //Please use better names tho + } + +Which would produce: + + [ByeAyeCye] + +The built-in layouts should cover 95% (totally legit stat) of use cases simply enough. But for special cases you can extend IVD with custom materials. + +## Models + +A model may define a hierarchy, but elements bound to specific model items don't necessarily have to reflect it directly, and are free to position items in a root element or separate window, for example. + +The IVD philosophy is that a model shouldn't *ridgedly* define the presentation. Contrast with HTML where absolutely everything is a part of the DOM in a very specific order and hierarchy, even unrelated models must be encoded in an arbitrary order. + +There are two types of elements in IVD. "Free elements", which are not bound to a model and there is exactly one instance, and "enumerated elements", which are bound to a model instance, and there can be zero or more of them (one for each instance). + +In the previous examples, you've seen elements declared as such: + + #name-optional {} + +This instantiates a single element. + +Suppose you have a model uncreatively named `my-model`: + + #an-enumerated-element -> my-model {} + +This creates a single instance of `an-enumerated-element` for every item in `my-model`. A "model" simply declares a list of model items. The process of binding elements to model items is known in IVD parlance as "enumeration", as elements are *enumerated* by the model. + +A model item can define strings, integers, trigger slots and states. All of which may be used by elements in IVD: + + # -> model-name + { + text: model.the-text; + + state model.a-state: + width: model.width-for-a-state; + + state this.clicked: + trigger: model.react-to-click; + } + +References to the model within an element are prefixed with the keyword `model` and not the model's identifier because it allows you to easily rename the model, use generic models in classes, and because I felt like it. + +Values from a model item that are used by IVD are always kept in sync. If the value changes in the model, the change is reflected in the IVD runtime. + +Model states can be manipulated directly by IVD code as well: + + # -> arbitrarily-named-model-42 + { + state x: + induce-state: model.a-model-state; + } + +Models themselves can contain child items, allowing for complex nested data structures. + +You can't position a free element within an enumerated element, you can however, position an enumerated element within a free element, or position an enumerated element within another enumerated element which share a model in common: + + #Nietzsche -> model-name; + + # -> model-name + { + position-within: Nietzsche; + } + +The runtime will find the correct instance of `Nietzsche` to position the anonymous element within. + +This actually works with any common ancestor, suppose the following: + + #an-elephant -> elephants + { + layout: vbox; + } + + #leg -> elephants::legs + { + position-within: an-elephant; + } + +This enumerates an element for each instance of `elephant` and each instance of `elephant::legs`. Notice that before the `position-within` attribute is set, the item `leg` has no visual relationship to `an-elephant`. Common model deduction is what allows you to position elements enumerated across a model, within elements enumerated by *that model's ancestor*. + +Suppose you want ordered items (As one often does). Perhaps the model has triggers defined for sorting the model according to different criteria. The order of elements in IVD can be bound to the model order: + + #an-elephant -> elephants + { + layout: vbox; + model-order: enable; //Sort child elements according to model + } + + #leg -> elephants::legs + { + position-within: an-elephant; + } + +The plan is to be able to rearrange items in IVD, and then have the new order backpropogated to the model as well. + +## Equation Solver + +IVD allows you to define scalar constraints as equations which are kept up-to-date automatically: + + #element1 + { + width: other-element.height * 2; + } + +The trouble with the above, is that it isn't flexible. What if you just really wanted for `element1.width == 200` to be true, but without violating the constraint? + + #element2 + { + state I-want-to-set-something: + set: element1 = 200; //error because it doesn't know what to do + } + +Wouldn't it be nice if you could get IVD to figure out what `other-element.height` needs to be in order for `element1.width == 200` to be true? + + #element1 + { + width: [other-element.height] * 2; //Declare which value in the expression is weak + } + + #element2 + { + state I-want-to-set-something: + set: element1 = 200; //Works, because now it knows to solve for other-element.height + } + + +What happens in the above is that the expression in `element1.width` is solved for `other-element.height`, and the result is backpropogated to `other-element.height` (Which may be defined as a variable or an expression with a weak value, it can be turtles all the way down). Once that is updated, the expression in `element1.width` is reevaluated. + +It's important to think of this as more of a suggestion than an absolute order. `other-element.height` might have a min/max constraint which rounds off the value being propogated, and then ***that*** value is what is observed when `element1.width` is reevaluated. Nothing is ever left in an inconsistent state. + +Scalars declared by a model can be back-propogated to as well: + + + #element1 -> my-model + { + width: [model.a-val] * 2; + } + +Everything always obeys the constraints as defined, but you also have the power to update the observed values arbitrarily, giving you the best of both worlds. + +## Classes + +Classes are very simple. They're really just templates from which attributes are copied: + + .class-name + { + color: blue; + } + + # : class-name + { + //color is blue + } + +Shorthand version if you don't need to declare anything in the body of the deriving element: + + # : class-name; + +An element can derive from an arbitrary number of classes, and the attributes are overriden in the order that the classes are declared: + + .another-class + { + color: yellow; + + state ::.IVD-Mouse-Motion: + color: green; + } + + # : another-class, class-name + { + //color is blue + + state ::.IVD-Mouse-Motion: + //color is green + } + + # : class-name, another-class //Class list order different + { + //color is yellow this time + + state ::.IVD-Mouse-Motion: + //color is still green because it's not in conflict + } + +## Remorial Classes for Code Reuse + +And last but *certainly* not least. + +Suppose you have the following construct: + + #contextual-dialog + { + layout: vbox; + named-cells: message-cell, input-cell, confirmation-cell; + } + + #message-area + { + position-within: contextual-dialog.message-cell; + layout: hbox; + named-cells: image-cell, text-cell; + } + + #message-image + { + position-within: message-area.image-cell; + image: "image-uri"; + } + + #message-text + { + position-within: message-area.text-cell; + text: "Enter info below"; + } + + //etc just use your imagination for the confirmation cell + +Everything is fine and good until... You need to reuse that. You can't simply use a class because a class only helps with a single element, and the above can only work with several elements. + +Remorial classes are a way of defining a "composite" element. You define the class as normal, and then attach "remoras" (get it?), which are just a special kind of element to it. Whenever an element derives from this class, a copy of each remora is also spun up as well, facilitating reuse of complex elements. + +And the syntax is quite simple: + + .contextual-dialog + { + layout: vbox; + named-cells: message-cell, input-cell, confirmation-cell; + } + + @contextual-dialog.message-area + { + position-within: @.message-cell; + layout: hbox; + named-cells: image-cell, text-cell; + } + + @conceptual-dialog.message-image + { + position-within: @::message-area.image-cell; + image: "image-uri"; + } + + @conceptual-dialog.message-text + { + position-within: @::message-area.text-cell; + text: "Enter info below"; + } + + //etc + + # : contextual-dialog; + +This is equivalent to the previous example, except that it is reusable, of course. + +Where the remora operator (`@`) is used within an element, it is substituted by the actual instance name given to the element which derives from the remorial class (Which is auto-generated in the above example as it's an anonymous element). + +One special feature of remora substitution is that you can address any remora in the "school" (get it???) from any other element in the school using the `@::element-name` syntax, which is substituted with the name generated for it by the compiler. This allows for a remorial class to export values from a child remora (see /src/tests/valid/remoravaluekeysubstitution.ivd) which are all otherwise unaddressable, but the implications of this are outside the scope of this little hoe-down. + +The remora example above is obviously incomplete. The biggest failing is that it really should be bound to a model in order to have a place to actually send input data and triggers for buttons. +Remoras work with nested models, and common parent deduction and all that good stuff as well. They're just an additional type of template which tag alongside otherwise normal classes. + +Again, remoras are just syntactic sugar, they are expanded by the compiler. The resulting elements are exactly the same as if they had been defined manually. A little bit of witchcraft and maybe some symbol substitution makes it all come together quite nicely~ + + +## But That's Not All! + +This is by no means a complete overview of the features developed or in development for IVD. We haven't even mentioned the (working!) animation system or the ability to declare expressions (which is to allow for complex widget interactions such as scrollbars or sliders affecting viewports, all defined within IVD), or the C (see /src/user_include/IVD_c.h) and C++ (see /src/user_include/IVD_cpp.h) bindings... It is simply meant to give you a taste for the project. + +# What's Working? + +In no particular order: + +- The compiler, which produces friendly error messages. +- Remorial class instantiation. +- Basic element styling. +- States and state expressions. +- The layout/material system. +- Text layouts. +- Models, enumeration, common ancestor deduction, etc. +- Animations of arbitrary scalar attributes. +- The equation solver (Not tested thoroughly enough for my tastes though). +- And a bunch of other stuff, probably. + + + +# Contributing + +\*sounds of deranged cackling echo in the distance\* + +please help + +# Credits + +Created and developed by Tracy Rust (tracy@enesda.com). + +# License + +IVD is licensed under the terms of the LGPL-3.0-only diff --git a/binarysourcegenerator.py b/binarysourcegenerator.py new file mode 100755 index 0000000..9858a70 --- /dev/null +++ b/binarysourcegenerator.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +#This file is part of the IVD project and is licensed under LGPL-3.0-only + + +from sys import argv + +blah, inputBinaryFile, includePath, variableName, outputName = argv + +output = [] +with open(inputBinaryFile, "rb") as f: + byte = f.read(1) + while byte: + output.append("0x" + byte.hex()) + byte = f.read(1) + +with open(outputName, "w") as f: + f.write("//This file was generated at build time.\n") + f.write("#include \"%s\"\n" % includePath) + f.write("const unsigned char %s[] = {\n" % variableName) + + length = len(output) + i = 1; + for char in output: + f.write(char) + if i != length: + f.write(",") + + if i and not i % 12: + f.write("\n") + elif i != length: + f.write(" ") + + i += 1 + + f.write("};\n") + f.write("const int %sSize = %d;\n" % (variableName, len(output))) diff --git a/contrib/fonts/LICENSE b/contrib/fonts/LICENSE new file mode 100644 index 0000000..aba73e8 --- /dev/null +++ b/contrib/fonts/LICENSE @@ -0,0 +1,102 @@ +Digitized data copyright (c) 2010 Google Corporation + with Reserved Font Arimo, Tinos and Cousine. +Copyright (c) 2012 Red Hat, Inc. + with Reserved Font Name Liberation. + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +PREAMBLE The goals of the Open Font License (OFL) are to stimulate +worldwide development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to provide +a free and open framework in which fonts may be shared and improved in +partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. +The fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + + + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. +This may include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components +as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting ? in part or in whole ? +any of the components of the Original Version, by changing formats or +by porting the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical writer +or other person who contributed to the Font Software. + + +PERMISSION & CONDITIONS + +Permission is hereby granted, free of charge, to any person obtaining a +copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components,in + Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, + redistributed and/or sold with any software, provided that each copy + contains the above copyright notice and this license. These can be + included either as stand-alone text files, human-readable headers or + in the appropriate machine-readable metadata fields within text or + binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font + Name(s) unless explicit written permission is granted by the + corresponding Copyright Holder. This restriction only applies to the + primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font + Software shall not be used to promote, endorse or advertise any + Modified Version, except to acknowledge the contribution(s) of the + Copyright Holder(s) and the Author(s) or with their explicit written + permission. + +5) The Font Software, modified or unmodified, in part or in whole, must + be distributed entirely under this license, and must not be distributed + under any other license. The requirement for fonts to remain under + this license does not apply to any document created using the Font + Software. + + + +TERMINATION +This license becomes null and void if any of the above conditions are not met. + + + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER +DEALINGS IN THE FONT SOFTWARE. + diff --git a/contrib/fonts/LiberationMono-Bold.ttf b/contrib/fonts/LiberationMono-Bold.ttf new file mode 100644 index 0000000..e508bd5 Binary files /dev/null and b/contrib/fonts/LiberationMono-Bold.ttf differ diff --git a/contrib/fonts/LiberationMono-BoldItalic.ttf b/contrib/fonts/LiberationMono-BoldItalic.ttf new file mode 100644 index 0000000..81227a1 Binary files /dev/null and b/contrib/fonts/LiberationMono-BoldItalic.ttf differ diff --git a/contrib/fonts/LiberationMono-Italic.ttf b/contrib/fonts/LiberationMono-Italic.ttf new file mode 100644 index 0000000..349cf9d Binary files /dev/null and b/contrib/fonts/LiberationMono-Italic.ttf differ diff --git a/contrib/fonts/LiberationMono-Regular.ttf b/contrib/fonts/LiberationMono-Regular.ttf new file mode 100644 index 0000000..a50a347 Binary files /dev/null and b/contrib/fonts/LiberationMono-Regular.ttf differ diff --git a/contrib/fonts/LiberationSans-Bold.ttf b/contrib/fonts/LiberationSans-Bold.ttf new file mode 100644 index 0000000..8f47a7f Binary files /dev/null and b/contrib/fonts/LiberationSans-Bold.ttf differ diff --git a/contrib/fonts/LiberationSans-BoldItalic.ttf b/contrib/fonts/LiberationSans-BoldItalic.ttf new file mode 100644 index 0000000..910de28 Binary files /dev/null and b/contrib/fonts/LiberationSans-BoldItalic.ttf differ diff --git a/contrib/fonts/LiberationSans-Italic.ttf b/contrib/fonts/LiberationSans-Italic.ttf new file mode 100644 index 0000000..633786f Binary files /dev/null and b/contrib/fonts/LiberationSans-Italic.ttf differ diff --git a/contrib/fonts/LiberationSans-Regular.ttf b/contrib/fonts/LiberationSans-Regular.ttf new file mode 100644 index 0000000..b73a0f5 Binary files /dev/null and b/contrib/fonts/LiberationSans-Regular.ttf differ diff --git a/contrib/fonts/LiberationSerif-Bold.ttf b/contrib/fonts/LiberationSerif-Bold.ttf new file mode 100644 index 0000000..d5fc679 Binary files /dev/null and b/contrib/fonts/LiberationSerif-Bold.ttf differ diff --git a/contrib/fonts/LiberationSerif-BoldItalic.ttf b/contrib/fonts/LiberationSerif-BoldItalic.ttf new file mode 100644 index 0000000..8cebef6 Binary files /dev/null and b/contrib/fonts/LiberationSerif-BoldItalic.ttf differ diff --git a/contrib/fonts/LiberationSerif-Italic.ttf b/contrib/fonts/LiberationSerif-Italic.ttf new file mode 100644 index 0000000..6aa9e51 Binary files /dev/null and b/contrib/fonts/LiberationSerif-Italic.ttf differ diff --git a/contrib/fonts/LiberationSerif-Regular.ttf b/contrib/fonts/LiberationSerif-Regular.ttf new file mode 100644 index 0000000..568e012 Binary files /dev/null and b/contrib/fonts/LiberationSerif-Regular.ttf differ diff --git a/contrib/rustutils/easyuniquepointer.h b/contrib/rustutils/easyuniquepointer.h new file mode 100644 index 0000000..934e332 --- /dev/null +++ b/contrib/rustutils/easyuniquepointer.h @@ -0,0 +1,42 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include + +namespace RustUtils +{ + +//I know it's frowned upon to inherit from std classes but fucking byte me. +//This is just for the trivial case where I want a deep copy and nothing fancy. +template +class DeepCopyableUnique : public std::unique_ptr +{ +public: + typedef std::unique_ptr Parent; + DeepCopyableUnique() noexcept {} + DeepCopyableUnique(const DeepCopyableUnique& other) noexcept: + Parent(other.get() ? std::make_unique(*other.get()) + : nullptr) + {} + + DeepCopyableUnique(const Parent&& other) noexcept: + Parent(other) + {} + + DeepCopyableUnique& operator=(const DeepCopyableUnique& other) noexcept + { + if(other.get()) + Parent::operator=(std::make_unique(*other.get())); + else Parent::reset(); + return *this; + } + + DeepCopyableUnique& makeCopy(const T& other) + { + Parent::operator=(std::make_unique(other)); + return *this; + } +}; + +}//RustUtils diff --git a/contrib/rustutils/lexcompare.h b/contrib/rustutils/lexcompare.h new file mode 100644 index 0000000..818b27e --- /dev/null +++ b/contrib/rustutils/lexcompare.h @@ -0,0 +1,353 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef RUSTUTILS_LEXCOMPARE +#define RUSTUTILS_LEXCOMPARE + +#include + +//Once upon a time I thought 7 outta be enough. But it proved insufficient, so then I made it 17. +//... Then that wasn't enough... So now it's 22... + +#define RUSTUTILS_MACRO_OVERLOAD(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, MACRO, ...) MACRO + +#define RUSTUTILS_UNPACK_FINAL(object, field) object . field + +#define RUSTUTILS_UNPACK22(object, field22, field21, field20, field19, field18, field17, field16, field15, field14, field13, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field22) , \ + RUSTUTILS_UNPACK_FINAL(object, field21) , \ + RUSTUTILS_UNPACK_FINAL(object, field20) , \ + RUSTUTILS_UNPACK_FINAL(object, field19) , \ + RUSTUTILS_UNPACK_FINAL(object, field18) , \ + RUSTUTILS_UNPACK_FINAL(object, field17) , \ + RUSTUTILS_UNPACK_FINAL(object, field16) , \ + RUSTUTILS_UNPACK_FINAL(object, field15) , \ + RUSTUTILS_UNPACK_FINAL(object, field14) , \ + RUSTUTILS_UNPACK_FINAL(object, field13) , \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK21(object, field21, field20, field19, field18, field17, field16, field15, field14, field13, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field21) , \ + RUSTUTILS_UNPACK_FINAL(object, field20) , \ + RUSTUTILS_UNPACK_FINAL(object, field19) , \ + RUSTUTILS_UNPACK_FINAL(object, field18) , \ + RUSTUTILS_UNPACK_FINAL(object, field17) , \ + RUSTUTILS_UNPACK_FINAL(object, field16) , \ + RUSTUTILS_UNPACK_FINAL(object, field15) , \ + RUSTUTILS_UNPACK_FINAL(object, field14) , \ + RUSTUTILS_UNPACK_FINAL(object, field13) , \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK20(object, field20, field19, field18, field17, field16, field15, field14, field13, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field20) , \ + RUSTUTILS_UNPACK_FINAL(object, field19) , \ + RUSTUTILS_UNPACK_FINAL(object, field18) , \ + RUSTUTILS_UNPACK_FINAL(object, field17) , \ + RUSTUTILS_UNPACK_FINAL(object, field16) , \ + RUSTUTILS_UNPACK_FINAL(object, field15) , \ + RUSTUTILS_UNPACK_FINAL(object, field14) , \ + RUSTUTILS_UNPACK_FINAL(object, field13) , \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK19(object, field19, field18, field17, field16, field15, field14, field13, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field19) , \ + RUSTUTILS_UNPACK_FINAL(object, field18) , \ + RUSTUTILS_UNPACK_FINAL(object, field17) , \ + RUSTUTILS_UNPACK_FINAL(object, field16) , \ + RUSTUTILS_UNPACK_FINAL(object, field15) , \ + RUSTUTILS_UNPACK_FINAL(object, field14) , \ + RUSTUTILS_UNPACK_FINAL(object, field13) , \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK18(object, field18, field17, field16, field15, field14, field13, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field18) , \ + RUSTUTILS_UNPACK_FINAL(object, field17) , \ + RUSTUTILS_UNPACK_FINAL(object, field16) , \ + RUSTUTILS_UNPACK_FINAL(object, field15) , \ + RUSTUTILS_UNPACK_FINAL(object, field14) , \ + RUSTUTILS_UNPACK_FINAL(object, field13) , \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK17(object, field17, field16, field15, field14, field13, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field17) , \ + RUSTUTILS_UNPACK_FINAL(object, field16) , \ + RUSTUTILS_UNPACK_FINAL(object, field15) , \ + RUSTUTILS_UNPACK_FINAL(object, field14) , \ + RUSTUTILS_UNPACK_FINAL(object, field13) , \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK16(object, field16, field15, field14, field13, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field16) , \ + RUSTUTILS_UNPACK_FINAL(object, field15) , \ + RUSTUTILS_UNPACK_FINAL(object, field14) , \ + RUSTUTILS_UNPACK_FINAL(object, field13) , \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK15(object, field15, field14, field13, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field15) , \ + RUSTUTILS_UNPACK_FINAL(object, field14) , \ + RUSTUTILS_UNPACK_FINAL(object, field13) , \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK14(object, field14, field13, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field14) , \ + RUSTUTILS_UNPACK_FINAL(object, field13) , \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK13(object, field13, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field13) , \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK12(object, field12, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field12) , \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK11(object, field11, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field11) , \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK10(object, field10, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field10) , \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK9(object, field9, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field9) , \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK8(object, field8, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field8) , \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK7(object, field7, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field7) , \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK6(object, field6, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field6) , \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK5(object, field5, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field5) , \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK4(object, field4, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field4) , \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK3(object, field3, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field3) , \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK2(object, field2, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field2) , \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK1(object, field1) \ + RUSTUTILS_UNPACK_FINAL(object, field1) + +#define RUSTUTILS_UNPACK(object, fields...) \ + RUSTUTILS_MACRO_OVERLOAD(fields, \ + RUSTUTILS_UNPACK22, \ + RUSTUTILS_UNPACK21, \ + RUSTUTILS_UNPACK20, \ + RUSTUTILS_UNPACK19, \ + RUSTUTILS_UNPACK18, \ + RUSTUTILS_UNPACK17, \ + RUSTUTILS_UNPACK16, \ + RUSTUTILS_UNPACK15, \ + RUSTUTILS_UNPACK14, \ + RUSTUTILS_UNPACK13, \ + RUSTUTILS_UNPACK12, \ + RUSTUTILS_UNPACK11, \ + RUSTUTILS_UNPACK10, \ + RUSTUTILS_UNPACK9, \ + RUSTUTILS_UNPACK8, \ + RUSTUTILS_UNPACK7, \ + RUSTUTILS_UNPACK6, \ + RUSTUTILS_UNPACK5, \ + RUSTUTILS_UNPACK4, \ + RUSTUTILS_UNPACK3, \ + RUSTUTILS_UNPACK2, \ + RUSTUTILS_UNPACK1)(object, fields) + + +#define RUSTUTILS_DEFINE_COMP(type, fields...) \ + friend bool operator<(const type & left, const type & right) \ + { \ + return std::tie(RUSTUTILS_UNPACK(left, fields)) < \ + std::tie(RUSTUTILS_UNPACK(right, fields)); \ + } \ + friend bool operator!=(const type & left, const type & right) \ + { \ + return left < right || right < left; \ + } \ + friend bool operator==(const type & left, const type & right) \ + { \ + return !(left != right); \ + } + +#endif // RUSTUTILS_LEXCOMPARE diff --git a/contrib/rustutils/routine.h b/contrib/rustutils/routine.h new file mode 100755 index 0000000..8ee3844 --- /dev/null +++ b/contrib/rustutils/routine.h @@ -0,0 +1,20 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef ROUTINE_H +#define ROUTINE_H + +namespace RustUtils +{ +namespace Routine +{ + +template +void appendContainer(T& left, const T& right) +{ + left.insert(left.end(), right.cbegin(), right.cend()); +} + +}//Routine +}//RustUtils + +#endif // ROUTINE_H diff --git a/contrib/test-images/jpeg_test_article.jpg b/contrib/test-images/jpeg_test_article.jpg new file mode 100755 index 0000000..e6d59f2 Binary files /dev/null and b/contrib/test-images/jpeg_test_article.jpg differ diff --git a/contrib/test-images/png_test_article.png b/contrib/test-images/png_test_article.png new file mode 100755 index 0000000..9ba6242 Binary files /dev/null and b/contrib/test-images/png_test_article.png differ diff --git a/ivd-config.cmake.in b/ivd-config.cmake.in new file mode 100644 index 0000000..fb60661 --- /dev/null +++ b/ivd-config.cmake.in @@ -0,0 +1,3 @@ +include(${CMAKE_CURRENT_LIST_DIR}/ivd-targets.cmake) + +set(IVD_INCLUDE_DIRS "@IVD_USER_INCLUDE_DIRS@") diff --git a/ivd.vim b/ivd.vim new file mode 100755 index 0000000..3153ab3 --- /dev/null +++ b/ivd.vim @@ -0,0 +1,34 @@ +"This file is part of the IVD project and is licensed under LGPL-3.0-only + +spec bleeding; + +" Custom syntax for the IVD language. +" IVD: Interactive Visual Design + +if exists("b:current_syntax") + finish +endif + +echom "IVD: Interactive Visual Design" + +syntax keyword ivdLabel state nextgroup=ivdCustomState skipwhite +syntax keyword ivdKeyword exclusive + +syntax keyword ivdAttributes position position-within margin layout +syntax keyword ivdAttributes width height greyout + +syntax match ivdCustomState "\i\+" contained +highlight link ivdCustomState Identifier + +syntax match ivdComment "\v//.*$" +highlight link ivdComment Comment + + +highlight link ivdLabel Label +highlight link ivdKeyword Keyword +highlight link ivdAttributes Type + + + +let b:current_syntax = "ivd" + diff --git a/src/attributebodytypes.h b/src/attributebodytypes.h new file mode 100644 index 0000000..64f88fe --- /dev/null +++ b/src/attributebodytypes.h @@ -0,0 +1,28 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include + +namespace IVD +{ + +typedef unsigned int KeyType; + +struct AttributeBodyTypes +{ + bool expression; + bool property; + bool stringLiteral; + bool userToken; + bool userTokenList; + bool stateKeyList; + bool singleScopedValueKey; + bool color; + bool unnatural; + bool positionWithin; +}; + +typedef std::map AttributeBodyTypeMap; + +}//IVD diff --git a/src/attributepositionpair.h b/src/attributepositionpair.h new file mode 100644 index 0000000..25a7dcb --- /dev/null +++ b/src/attributepositionpair.h @@ -0,0 +1,22 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef ATTRIBUTEPOSITIONPAIR_H +#define ATTRIBUTEPOSITIONPAIR_H + +namespace IVD +{ + +class ReferenceAttributeSet; + +struct AttributePositionPair +{ + int position; + ReferenceAttributeSet* attrs; + + AttributePositionPair() {} + AttributePositionPair(const int p, ReferenceAttributeSet* a): position(p), attrs(a) {} +}; + +}//IVD + +#endif // ATTRIBUTEPOSITIONPAIR_H diff --git a/src/binaryexpressionprinter.h b/src/binaryexpressionprinter.h new file mode 100644 index 0000000..66c08fb --- /dev/null +++ b/src/binaryexpressionprinter.h @@ -0,0 +1,195 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace IVD +{ + +template +std::string generateExpressionPrintout(const Node& root) +{ + //Form the tree + //*___ + // | + // ___/___ + //| | + //3 ___*____ + // | | + // 4 [other.key] + + const int minPadding = 3; + const int minWidth = 7; + const int barPadding = 3; + + struct ShadowNode + { + std::string literal; + + int width; + int centerOffset; + int intraNodePadding; + int barWidth; + + std::unique_ptr left; + std::unique_ptr right; + + bool isLeaf() const + { return !left && !right; } + }; + + std::function createShadowTree = [&](const Node& node) -> ShadowNode + { + ShadowNode shadowNode; + + shadowNode.literal = node.printoutThySelf(); + + if(node.left) shadowNode.left = std::make_unique(createShadowTree(*node.left)); + if(node.right) shadowNode.right = std::make_unique(createShadowTree(*node.right)); + + assert(node.left && node.right || !node.left && !node.right); + + if(shadowNode.isLeaf()) + { + if(shadowNode.literal.size() <= minWidth) shadowNode.width = minWidth; + else shadowNode.width = shadowNode.literal.size(); //TODO Make unicode safe (ICU) + + shadowNode.intraNodePadding = 0; + shadowNode.centerOffset = shadowNode.width / 2; + } + else + { + const int leftWidth = shadowNode.left->width; + const int leftCenter = shadowNode.left->centerOffset; + + const int rightWidth = shadowNode.right->width; + const int rightCenter = shadowNode.right->centerOffset; + + const int naturalWidth = leftWidth + rightWidth + minPadding; + + if(naturalWidth < minWidth) + { + shadowNode.width = minWidth; + shadowNode.intraNodePadding = minPadding - (leftWidth + rightWidth); + } + else + { + shadowNode.width = naturalWidth; + shadowNode.intraNodePadding = minPadding; + } + + shadowNode.barWidth = leftWidth - leftCenter + shadowNode.intraNodePadding + rightCenter; + + //Make a quick adjustment for people that think it's cool to have an operator + // longer than 1 character. + const int minBarWidth = barPadding * 2 + shadowNode.literal.size(); + if(shadowNode.barWidth < minBarWidth) + { + const int difference = minBarWidth - shadowNode.barWidth; + + shadowNode.width += difference; + shadowNode.intraNodePadding += difference; + shadowNode.barWidth = minBarWidth; + } + + shadowNode.centerOffset = shadowNode.barWidth / 2 + leftCenter; + } + + return shadowNode; + }; + + const ShadowNode shadowTree = createShadowTree(root); + + struct Matrix + { + int x; + int y; + std::vector lines; + }; + std::vector myMatrices; + + std::function developMatrices = + [&](const ShadowNode& myNode, const int xoffset, const int yoffset) -> void + { + Matrix myMatrix; + myMatrix.x = xoffset; + myMatrix.y = yoffset; + + const int nodeYoffset = yoffset + 2; + + myMatrix.lines.emplace_back(); + std::string& firstLine = myMatrix.lines.back(); + + if(!myNode.isLeaf()) + { + std::string secondLine; + + firstLine.append(myNode.centerOffset, ' '); + firstLine.push_back('|'); + + const int childLeftOffset = myNode.left->centerOffset; + + secondLine.append(childLeftOffset, ' '); + secondLine.append(myNode.barWidth + 1, '.'); //+ 1 because truncation + + const int opPosition = myNode.centerOffset - myNode.literal.size() / 2; + secondLine.replace(opPosition, myNode.literal.size(), myNode.literal); + + myMatrix.lines.push_back(secondLine); + } + else + { + firstLine.append(myNode.centerOffset, ' '); + firstLine.push_back('|'); + myMatrix.lines.push_back(myNode.literal); + } + + myMatrices.push_back(myMatrix); + + if(!myNode.isLeaf()) + { + developMatrices(*myNode.left, xoffset, nodeYoffset); + developMatrices(*myNode.right, + xoffset + myNode.left->width + myNode.intraNodePadding, + nodeYoffset); + } + }; + + developMatrices(shadowTree, 0, 0); + + std::vector lineBuffer; + + //Composite matrices + for(Matrix myMatrix : myMatrices) + { + while(myMatrix.lines.size() + myMatrix.y > lineBuffer.size()) + lineBuffer.emplace_back(); + + for(int i = 0; i != myMatrix.lines.size(); ++i) + { + const int yoffset = myMatrix.y + i; + std::string& outputLine = lineBuffer[yoffset]; + std::string& lineSection = myMatrix.lines[i]; + const int xoffset = myMatrix.x; + + if(lineSection.size() + xoffset > outputLine.size()) + outputLine.append(lineSection.size() + xoffset - outputLine.size(), ' '); + + outputLine.replace(xoffset, lineSection.size(), lineSection); + } + } + + //Finally. :) + std::stringstream printout; + for(const std::string line : lineBuffer) printout << line << std::endl; + + return printout.str(); +}; + +}//IVD diff --git a/src/canvas.h b/src/canvas.h new file mode 100644 index 0000000..bd1276f --- /dev/null +++ b/src/canvas.h @@ -0,0 +1,92 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef CANVAS_H +#define CANVAS_H + +#include +#include +#include "geometry.h" +#include "color.h" + +namespace IVD +{ + +class DisplayItem; + +struct Bitmap +{ + int stride; //Is stride really necessary at this layer of abstraction? + int width; + int height; + int channels; + unsigned char* data; +}; + +class Canvas +{ +protected: + //Okay so I promise this is for a real reason and not because I'm too lazy + // to add clips this to all the drawing functions, which theoretically + // would be "faster". + //The reason is that clips are cumulative, and need to be unwound, and + // children shouldn't know about parent clips. + std::vector clips; + + Coords offset; + + double alpha = 255; + +public: + void pushClip(const Rect clip) + { clips.push_back(clip); } + + void popClip() + { clips.pop_back(); } + + void setAlpha(const double theAlpha) + { alpha = theAlpha; } + + double getAlpha() + { return alpha; } + + void setOffset(const Coords theOffset) + { offset = theOffset; } + + void resetOffset() + { offset = Coords(); } + + + virtual void setSize(const Dimens size) = 0; + virtual Dimens getSize() = 0; + virtual bool ready() = 0; + + virtual void clear() = 0; + + virtual void fillRect(Rect r, Color theColor) = 0; + virtual void strokeRect(Rect r, int size, Color theColor, Color::AlphaType alpha) = 0; + virtual void drawLine(Coords start, Coords end, int size, Color theColor, Color::AlphaType alpha) = 0; + virtual void drawGradient(Rect box, + Color toftColor, + Color::AlphaType toftAlpha, + Color boriColor, + Color::AlphaType boriAlpha, + Angle theAngle) = 0; + virtual void drawDropShadow(Rect box, int size, Color theColor, Color::AlphaType alpha) = 0; + + virtual void drawBitmapRGBoptionalA(Coords dest, + int stride, + int width, + int height, + int channels, + unsigned char* data) = 0; + + virtual void drawText(Coords origin, const std::string text, DisplayItem* style) = 0; + + virtual void flush() = 0; + virtual Bitmap getBitmap() = 0; +}; + +}//IVD + + +#endif // CANVAS_H diff --git a/src/cbindings.cpp b/src/cbindings.cpp new file mode 100644 index 0000000..2562dee --- /dev/null +++ b/src/cbindings.cpp @@ -0,0 +1,260 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "environment.h" +#include "displayitem.h" +#include "specific_driver_sdl/sdldriver.h" + +//I've gone most of my life without using reinterpret_cast, and now look at me. +//Look at what I've become. +//I'm sorry uncle. +//I'm so sorry. + + +static IVD::Environment* castEnv(IVD_Environment* environment) +{ return reinterpret_cast(environment); } +static IVD::WidgetWrapper* castWidget(IVD_Widget* widget) +{ return reinterpret_cast(widget); } +static IVD_Widget* castWidget(IVD::WidgetWrapper* widget) +{ return reinterpret_cast(widget); } +static IVD::Dimens* castDimens(IVD_Dimens* space) +{ return reinterpret_cast(space); } + +static IVD_Dimens* castDimens(IVD::Dimens* space) +{ return reinterpret_cast(space); } +static IVD::Coords* castPoint(IVD_Coords* point) +{ return reinterpret_cast(point); } + +static IVD_Coords* castPoint(IVD::Coords* point) +{ return reinterpret_cast(point); } + +static IVD::DisplayItem* cast(IVD_Element* elem) +{ return reinterpret_cast(elem); } + +static const IVD::DisplayItem* cast(const IVD_Element* elem) +{ return reinterpret_cast(elem); } + + +extern "C" +{ +#include "user_include/IVD_c.h" + + +//--------------------------------------------------------------------------------------Environment + +IVD_Environment* IVD_create_environment() +{ return reinterpret_cast(new IVD::Environment()); } +void IVD_destroy_environment(IVD_Environment* environment) +{ delete castEnv(environment); } + +int IVD_environment_load_file(IVD_Environment* environment, const char* path) +{ + auto* properEnv = reinterpret_cast(environment); + return properEnv->loadFromIVDFile(path); +} +const char* IVD_environment_get_compiler_errors(IVD_Environment* environment) +{ + auto* properEnv = reinterpret_cast(environment); + return properEnv->getCompilerErrors(); +} +void IVD_environment_run(IVD_Environment* environment) +{ + auto* properEnv = reinterpret_cast(environment); + + //And this is where we take control! 🎊 + properEnv->run(); +} + +void IVD_environment_register_widget(IVD_Environment* environment, + const char* name, + IVD_Widget* (*ctor)(IVD_Environment*), + void (*dtor)(IVD_Widget*), + int (*getFillPrecedence)(IVD_Widget*, const int), + void (*shape)(IVD_Widget*, IVD_GeometryProposal*), + void (*draw)(IVD_Widget*, IVD_Canvas*),//canbe null + IVD_Dimens* (*getSpace)(IVD_Widget*), + int (*detectCollisionPoint)(IVD_Widget*, IVD_Coords*), //canbe null + void (*bubbler)(IVD_Widget*), + void (*triggerHandler)(IVD_Widget*, const char*)) +{ castEnv(environment)->registerWidgetBlueprints(name, {name, true, ctor, dtor, getFillPrecedence, shape, getSpace, draw, bubbler, detectCollisionPoint, triggerHandler}); } + +//IVD manages widget lifetimes so they can be "deleted later" +IVD_Widget* IVD_environment_widget_create(IVD_Environment* environment, const char* name, IVD_Widget* parent) +{ return castEnv(environment)->createWidget(name, parent); } + +IVD_Element* IVD_environment_create_element_from_class(IVD_Environment* environment, const char* className, IVD_Widget* parent) +{ return castEnv(environment)->createIVDelementFromClass(className, parent); } + +void IVD_environment_widget_destroy(IVD_Environment* environment, IVD_Widget* widget) +{ castEnv(environment)->destroyWidget(widget); } + +void IVD_environment_element_destroy(IVD_Environment* environment, IVD_Widget* parent, IVD_Element* elem) +{ castEnv(environment)->destroyIVDelement(parent, elem); } + + +void IVD_environment_register_layout(IVD_Environment* environment, + const char* name, + IVD_Widget* (*ctor)(IVD_Environment*), + void (*dtor)(IVD_Widget*), + int (*getFillPrecedence)(IVD_Widget*, const int), + void (*shape)(IVD_Widget*, IVD_GeometryProposal*), + void (*draw)(IVD_Widget*, IVD_Canvas*), + IVD_Dimens* (*getSpace)(IVD_Widget*), + void (*bubbler)(IVD_Widget*)) +{ castEnv(environment)->registerLayoutBlueprints(name, {name, false, ctor, dtor, getFillPrecedence, shape, getSpace, draw, bubbler, nullptr, nullptr}); } + + + +//----------------------------------------------------------------------------------------------Dust Bindings + + + +IVD_Dimens* IVD_dimens_alloc() +{ return castDimens(new IVD::Dimens()); } + +void IVD_dimens_free(IVD_Dimens* space) +{ delete castDimens(space); } + +int* IVD_dimens_w(IVD_Dimens* space) +{ return &castDimens(space)->w; } +int* IVD_dimens_h(IVD_Dimens* space) +{ return &castDimens(space)->h; } + + + +IVD_Coords* IVD_coords_alloc() +{ return castPoint(new IVD::Coords()); } + +void IVD_coords_free(IVD_Coords* point) +{ delete castPoint(point); } + +int* IVD_coords_x(IVD_Coords* point) +{ return &castPoint(point)->x; } +int* IVD_coords_y(IVD_Coords* point) +{ return &castPoint(point)->y; } + + +static IVD::Rect* castRect(IVD_Rect* rect) +{ return reinterpret_cast(rect); } + +IVD_Rect* IVD_rect_alloc() +{ return reinterpret_cast(new IVD::Rect()); } + +void IVD_rect_free(IVD_Rect* rect) +{ delete castRect(rect); } + +IVD_Dimens* IVD_rect_get_dimens(IVD_Rect* rect) +{ return castDimens(&castRect(rect)->d); } +IVD_Coords* IVD_rect_get_coords(IVD_Rect* rect) +{ return castPoint(&castRect(rect)->c); } +void IVD_rect_set_space(IVD_Rect* rect, IVD_Dimens* space) +{ castRect(rect)->d = *castDimens(space); } +void IVD_rect_set_point(IVD_Rect* rect, IVD_Coords* point) +{ castRect(rect)->c = *castPoint(point); } + + +//-------------------------------------------------------------------------------------------GeometryProposal +static IVD::GeometryProposal* castGeoprop(IVD_GeometryProposal* prop) +{ return reinterpret_cast(prop); } + +IVD_GeometryProposal* IVD_geoprop_alloc() +{ return reinterpret_cast(new IVD::GeometryProposal()); } + +void IVD_geoprop_free(IVD_GeometryProposal* prop) +{ delete castGeoprop(prop); } + +IVD_Dimens* IVD_geoprop_proposed_space(IVD_GeometryProposal* prop) +{ return castDimens(&castGeoprop(prop)->proposedDimensions); } + +int* IVD_geoprop_expand_horizontal(IVD_GeometryProposal* prop) +{ return &castGeoprop(prop)->expandForAngle(IVD::Angle::Horizontal); } + +int* IVD_geoprop_expand_vertical(IVD_GeometryProposal* prop) +{ return &castGeoprop(prop)->shrinkForAngle(IVD::Angle::Vertical); } + +int* IVD_geoprop_shrink_horizontal(IVD_GeometryProposal* prop) +{ return &castGeoprop(prop)->shrinkForAngle(IVD::Angle::Horizontal); } + +int* IVD_geoprop_shrink_vertical(IVD_GeometryProposal* prop) +{ return &castGeoprop(prop)->shrinkForAngle(IVD::Angle::Vertical); } + +int IVD_geoprop_verify_compliance(IVD_GeometryProposal* prop, IVD_Dimens* space) +{ return castGeoprop(prop)->verifyCompliance(*castDimens(space)); } + +void IVD_geoprop_round_conflicts(IVD_GeometryProposal* prop, IVD_Dimens* space) +{ *castDimens(space) = castGeoprop(prop)->roundConflicts(*castDimens(space)); } + +//-----------------------------------------------------------------------------------------------------Widget +IVD_Dimens* IVD_element_get_dimens(const IVD_Element* elem) +{ + thread_local static IVD::Dimens dimens; + dimens = cast(elem)->getViewportDimens(); + return castDimens(&dimens); +} + +int IVD_element_get_fill_precedence(IVD_Element* elem, int angle) +{ + const auto fillPrec = + cast(elem)->computerFillPrecedenceForAngle(static_cast(angle)); + return static_cast(fillPrec); +} + +void IVD_element_shape(IVD_Element* elem, IVD_GeometryProposal* prop) +{ cast(elem)->shape(prop); } + +void IVD_element_set_offset(IVD_Element* elem, IVD_Coords* coords) +{ cast(elem)->setOffset(coords); } + +void IVD_element_draw(IVD_Element* elem) +{ cast(elem)->render(); } + +void IVD_element_bubble(IVD_Element* elem) +{ cast(elem)->updateHover(); } + +IVD_Element* IVD_widget_get_underlying_element(IVD_Environment* environment, IVD_Widget* widget) +{ return reinterpret_cast(castEnv(environment)->getUnderlyingDisplayItemForWidget(widget)); } + +//On requiring widget instead of parent element here. +// ehhhh it doesn't make a whole lot of difference. +//But this does kind of prevent you from going deeper into the +// tree than one level (you can't recursively get child elements) +// which I kind of like. +void IVD_widget_get_child_elements(IVD_Environment* environment, IVD_Widget* widget, IVD_Element*** result, int* size) +{ + thread_local static std::vector resultContainer; + IVD::DisplayItem* item = castEnv(environment)->getUnderlyingDisplayItemForWidget(widget); + + resultContainer = item->getChildWidgetInStampOrder(); + + *size = resultContainer.size(); + *result = &resultContainer[0]; +} + +IVD_Element* IVD_widget_get_child_element_for_named_cell(IVD_Environment* environment, IVD_Widget* parent, const char* name) +{ + IVD::DisplayItem* item = castEnv(environment)->getUnderlyingDisplayItemForWidget(parent); + return item->getChildElementForNamedCell(name); +} +//--------------------Accessors + +void IVD_canvas_draw_image(IVD_Canvas* canvas, + int x, + int y, + int width, + int height, + int stride, + int channels, + unsigned char* data) +{ + IVD::Canvas* myCanvas = reinterpret_cast(canvas); + + //absolute drawing offset is already set by the + // owning DisplayItem, the x,y here are relative + myCanvas->drawBitmapRGBoptionalA(IVD::Coords(x,y), + stride, + width, + height, + channels, + data); +} + +}//extern "C" diff --git a/src/codeposition.h b/src/codeposition.h new file mode 100644 index 0000000..cb8a1d9 --- /dev/null +++ b/src/codeposition.h @@ -0,0 +1,18 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include "rustutils/lexcompare.h" + +namespace IVD +{ + +struct CodePosition +{ + int line; + int column; + + RUSTUTILS_DEFINE_COMP(CodePosition, line, column) +}; + +}//IVD diff --git a/src/color.cpp b/src/color.cpp new file mode 100644 index 0000000..ef6f5e8 --- /dev/null +++ b/src/color.cpp @@ -0,0 +1,817 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "color.h" + +#include +#include + +std::string IVD::Color::generateHexPrint() const +{ + auto convertColor = [&](uint8_t theColor) -> std::string + { + std::stringstream miniStream; + miniStream << std::setfill('0') << std::setw(2) << std::hex << (int)theColor; + + return miniStream.str(); + }; + + std::string hexedColor = "#"; + hexedColor += convertColor(red); + hexedColor += convertColor(green); + hexedColor += convertColor(blue); + + return hexedColor; +} + +std::string IVD::Color::generateDecPrint() const +{ + std::stringstream stream; + stream << (int)red << ", " << (int)green << ", " << (int)blue; + return stream.str(); +} + +IVD::Color::Color(const std::string& key): red(0), green(0), blue(0) +{ + if(key == "snow") *this = Color(255, 250, 250); + if(key == "ghost white") *this = Color(248, 248, 255); + if(key == "GhostWhite") *this = Color(248, 248, 255); + if(key == "white smoke") *this = Color(245, 245, 245); + if(key == "WhiteSmoke") *this = Color(245, 245, 245); + if(key == "gainsboro") *this = Color(220, 220, 220); + if(key == "floral white") *this = Color(255, 250, 240); + if(key == "FloralWhite") *this = Color(255, 250, 240); + if(key == "old lace") *this = Color(253, 245, 230); + if(key == "OldLace") *this = Color(253, 245, 230); + if(key == "linen") *this = Color(250, 240, 230); + if(key == "antique white") *this = Color(250, 235, 215); + if(key == "AntiqueWhite") *this = Color(250, 235, 215); + if(key == "papaya whip") *this = Color(255, 239, 213); + if(key == "PapayaWhip") *this = Color(255, 239, 213); + if(key == "blanched almond") *this = Color(255, 235, 205); + if(key == "BlanchedAlmond") *this = Color(255, 235, 205); + if(key == "bisque") *this = Color(255, 228, 196); + if(key == "peach puff") *this = Color(255, 218, 185); + if(key == "PeachPuff") *this = Color(255, 218, 185); + if(key == "navajo white") *this = Color(255, 222, 173); + if(key == "NavajoWhite") *this = Color(255, 222, 173); + if(key == "moccasin") *this = Color(255, 228, 181); + if(key == "cornsilk") *this = Color(255, 248, 220); + if(key == "ivory") *this = Color(255, 255, 240); + if(key == "lemon chiffon") *this = Color(255, 250, 205); + if(key == "LemonChiffon") *this = Color(255, 250, 205); + if(key == "seashell") *this = Color(255, 245, 238); + if(key == "honeydew") *this = Color(240, 255, 240); + if(key == "mint cream") *this = Color(245, 255, 250); + if(key == "MintCream") *this = Color(245, 255, 250); + if(key == "azure") *this = Color(240, 255, 255); + if(key == "alice blue") *this = Color(240, 248, 255); + if(key == "AliceBlue") *this = Color(240, 248, 255); + if(key == "lavender") *this = Color(230, 230, 250); + if(key == "lavender blush") *this = Color(255, 240, 245); + if(key == "LavenderBlush") *this = Color(255, 240, 245); + if(key == "misty rose") *this = Color(255, 228, 225); + if(key == "MistyRose") *this = Color(255, 228, 225); + if(key == "white") *this = Color(255, 255, 255); + if(key == "black") *this = Color(0, 0, 0); + if(key == "dark slate gray") *this = Color(47, 79, 79); + if(key == "DarkSlateGray") *this = Color(47, 79, 79); + if(key == "dark slate grey") *this = Color(47, 79, 79); + if(key == "DarkSlateGrey") *this = Color(47, 79, 79); + if(key == "dim gray") *this = Color(105, 105, 105); + if(key == "DimGray") *this = Color(105, 105, 105); + if(key == "dim grey") *this = Color(105, 105, 105); + if(key == "DimGrey") *this = Color(105, 105, 105); + if(key == "slate gray") *this = Color(112, 128, 144); + if(key == "SlateGray") *this = Color(112, 128, 144); + if(key == "slate grey") *this = Color(112, 128, 144); + if(key == "SlateGrey") *this = Color(112, 128, 144); + if(key == "light slate gray") *this = Color(119, 136, 153); + if(key == "LightSlateGray") *this = Color(119, 136, 153); + if(key == "light slate grey") *this = Color(119, 136, 153); + if(key == "LightSlateGrey") *this = Color(119, 136, 153); + if(key == "gray") *this = Color(190, 190, 190); + if(key == "grey") *this = Color(190, 190, 190); + if(key == "x11 gray") *this = Color(190, 190, 190); + if(key == "X11Gray") *this = Color(190, 190, 190); + if(key == "x11 grey") *this = Color(190, 190, 190); + if(key == "X11Grey") *this = Color(190, 190, 190); + if(key == "web gray") *this = Color(128, 128, 128); + if(key == "WebGray") *this = Color(128, 128, 128); + if(key == "web grey") *this = Color(128, 128, 128); + if(key == "WebGrey") *this = Color(128, 128, 128); + if(key == "light grey") *this = Color(211, 211, 211); + if(key == "LightGrey") *this = Color(211, 211, 211); + if(key == "light gray") *this = Color(211, 211, 211); + if(key == "LightGray") *this = Color(211, 211, 211); + if(key == "midnight blue") *this = Color(25, 25, 112); + if(key == "MidnightBlue") *this = Color(25, 25, 112); + if(key == "navy") *this = Color(0, 0, 128); + if(key == "navy blue") *this = Color(0, 0, 128); + if(key == "NavyBlue") *this = Color(0, 0, 128); + if(key == "cornflower blue") *this = Color(100, 149, 237); + if(key == "CornflowerBlue") *this = Color(100, 149, 237); + if(key == "dark slate blue") *this = Color(72, 61, 139); + if(key == "DarkSlateBlue") *this = Color(72, 61, 139); + if(key == "slate blue") *this = Color(106, 90, 205); + if(key == "SlateBlue") *this = Color(106, 90, 205); + if(key == "medium slate blue") *this = Color(123, 104, 238); + if(key == "MediumSlateBlue") *this = Color(123, 104, 238); + if(key == "light slate blue") *this = Color(132, 112, 255); + if(key == "LightSlateBlue") *this = Color(132, 112, 255); + if(key == "medium blue") *this = Color(0, 0, 205); + if(key == "MediumBlue") *this = Color(0, 0, 205); + if(key == "royal blue") *this = Color(65, 105, 225); + if(key == "RoyalBlue") *this = Color(65, 105, 225); + if(key == "blue") *this = Color(0, 0, 255); + if(key == "dodger blue") *this = Color(30, 144, 255); + if(key == "DodgerBlue") *this = Color(30, 144, 255); + if(key == "deep sky blue") *this = Color(0, 191, 255); + if(key == "DeepSkyBlue") *this = Color(0, 191, 255); + if(key == "sky blue") *this = Color(135, 206, 235); + if(key == "SkyBlue") *this = Color(135, 206, 235); + if(key == "light sky blue") *this = Color(135, 206, 250); + if(key == "LightSkyBlue") *this = Color(135, 206, 250); + if(key == "steel blue") *this = Color(70, 130, 180); + if(key == "SteelBlue") *this = Color(70, 130, 180); + if(key == "light steel blue") *this = Color(176, 196, 222); + if(key == "LightSteelBlue") *this = Color(176, 196, 222); + if(key == "light blue") *this = Color(173, 216, 230); + if(key == "LightBlue") *this = Color(173, 216, 230); + if(key == "powder blue") *this = Color(176, 224, 230); + if(key == "PowderBlue") *this = Color(176, 224, 230); + if(key == "pale turquoise") *this = Color(175, 238, 238); + if(key == "PaleTurquoise") *this = Color(175, 238, 238); + if(key == "dark turquoise") *this = Color(0, 206, 209); + if(key == "DarkTurquoise") *this = Color(0, 206, 209); + if(key == "medium turquoise") *this = Color(72, 209, 204); + if(key == "MediumTurquoise") *this = Color(72, 209, 204); + if(key == "turquoise") *this = Color(64, 224, 208); + if(key == "cyan") *this = Color(0, 255, 255); + if(key == "aqua") *this = Color(0, 255, 255); + if(key == "light cyan") *this = Color(224, 255, 255); + if(key == "LightCyan") *this = Color(224, 255, 255); + if(key == "cadet blue") *this = Color(95, 158, 160); + if(key == "CadetBlue") *this = Color(95, 158, 160); + if(key == "medium aquamarine") *this = Color(102, 205, 170); + if(key == "MediumAquamarine") *this = Color(102, 205, 170); + if(key == "aquamarine") *this = Color(127, 255, 212); + if(key == "dark green") *this = Color(0, 100, 0); + if(key == "DarkGreen") *this = Color(0, 100, 0); + if(key == "dark olive green") *this = Color(85, 107, 47); + if(key == "DarkOliveGreen") *this = Color(85, 107, 47); + if(key == "dark sea green") *this = Color(143, 188, 143); + if(key == "DarkSeaGreen") *this = Color(143, 188, 143); + if(key == "sea green") *this = Color(46, 139, 87); + if(key == "SeaGreen") *this = Color(46, 139, 87); + if(key == "medium sea green") *this = Color(60, 179, 113); + if(key == "MediumSeaGreen") *this = Color(60, 179, 113); + if(key == "light sea green") *this = Color(32, 178, 170); + if(key == "LightSeaGreen") *this = Color(32, 178, 170); + if(key == "pale green") *this = Color(152, 251, 152); + if(key == "PaleGreen") *this = Color(152, 251, 152); + if(key == "spring green") *this = Color(0, 255, 127); + if(key == "SpringGreen") *this = Color(0, 255, 127); + if(key == "lawn green") *this = Color(124, 252, 0); + if(key == "LawnGreen") *this = Color(124, 252, 0); + if(key == "green") *this = Color(0, 255, 0); + if(key == "lime") *this = Color(0, 255, 0); + if(key == "x11 green") *this = Color(0, 255, 0); + if(key == "X11Green") *this = Color(0, 255, 0); + if(key == "web green") *this = Color(0, 128, 0); + if(key == "WebGreen") *this = Color(0, 128, 0); + if(key == "chartreuse") *this = Color(127, 255, 0); + if(key == "medium spring green") *this = Color(0, 250, 154); + if(key == "MediumSpringGreen") *this = Color(0, 250, 154); + if(key == "green yellow") *this = Color(173, 255, 47); + if(key == "GreenYellow") *this = Color(173, 255, 47); + if(key == "lime green") *this = Color(50, 205, 50); + if(key == "LimeGreen") *this = Color(50, 205, 50); + if(key == "yellow green") *this = Color(154, 205, 50); + if(key == "YellowGreen") *this = Color(154, 205, 50); + if(key == "forest green") *this = Color(34, 139, 34); + if(key == "ForestGreen") *this = Color(34, 139, 34); + if(key == "olive drab") *this = Color(107, 142, 35); + if(key == "OliveDrab") *this = Color(107, 142, 35); + if(key == "dark khaki") *this = Color(189, 183, 107); + if(key == "DarkKhaki") *this = Color(189, 183, 107); + if(key == "khaki") *this = Color(240, 230, 140); + if(key == "pale goldenrod") *this = Color(238, 232, 170); + if(key == "PaleGoldenrod") *this = Color(238, 232, 170); + if(key == "light goldenrod yellow") *this = Color(250, 250, 210); + if(key == "LightGoldenrodYellow") *this = Color(250, 250, 210); + if(key == "light yellow") *this = Color(255, 255, 224); + if(key == "LightYellow") *this = Color(255, 255, 224); + if(key == "yellow") *this = Color(255, 255, 0); + if(key == "gold") *this = Color(255, 215, 0); + if(key == "light goldenrod") *this = Color(238, 221, 130); + if(key == "LightGoldenrod") *this = Color(238, 221, 130); + if(key == "goldenrod") *this = Color(218, 165, 32); + if(key == "dark goldenrod") *this = Color(184, 134, 11); + if(key == "DarkGoldenrod") *this = Color(184, 134, 11); + if(key == "rosy brown") *this = Color(188, 143, 143); + if(key == "RosyBrown") *this = Color(188, 143, 143); + if(key == "indian red") *this = Color(205, 92, 92); + if(key == "IndianRed") *this = Color(205, 92, 92); + if(key == "saddle brown") *this = Color(139, 69, 19); + if(key == "SaddleBrown") *this = Color(139, 69, 19); + if(key == "sienna") *this = Color(160, 82, 45); + if(key == "peru") *this = Color(205, 133, 63); + if(key == "burlywood") *this = Color(222, 184, 135); + if(key == "beige") *this = Color(245, 245, 220); + if(key == "wheat") *this = Color(245, 222, 179); + if(key == "sandy brown") *this = Color(244, 164, 96); + if(key == "SandyBrown") *this = Color(244, 164, 96); + if(key == "tan") *this = Color(210, 180, 140); + if(key == "chocolate") *this = Color(210, 105, 30); + if(key == "firebrick") *this = Color(178, 34, 34); + if(key == "brown") *this = Color(165, 42, 42); + if(key == "dark salmon") *this = Color(233, 150, 122); + if(key == "DarkSalmon") *this = Color(233, 150, 122); + if(key == "salmon") *this = Color(250, 128, 114); + if(key == "light salmon") *this = Color(255, 160, 122); + if(key == "LightSalmon") *this = Color(255, 160, 122); + if(key == "orange") *this = Color(255, 165, 0); + if(key == "dark orange") *this = Color(255, 140, 0); + if(key == "DarkOrange") *this = Color(255, 140, 0); + if(key == "coral") *this = Color(255, 127, 80); + if(key == "light coral") *this = Color(240, 128, 128); + if(key == "LightCoral") *this = Color(240, 128, 128); + if(key == "tomato") *this = Color(255, 99, 71); + if(key == "orange red") *this = Color(255, 69, 0); + if(key == "OrangeRed") *this = Color(255, 69, 0); + if(key == "red") *this = Color(255, 0, 0); + if(key == "hot pink") *this = Color(255, 105, 180); + if(key == "HotPink") *this = Color(255, 105, 180); + if(key == "deep pink") *this = Color(255, 20, 147); + if(key == "DeepPink") *this = Color(255, 20, 147); + if(key == "pink") *this = Color(255, 192, 203); + if(key == "light pink") *this = Color(255, 182, 193); + if(key == "LightPink") *this = Color(255, 182, 193); + if(key == "pale violet red") *this = Color(219, 112, 147); + if(key == "PaleVioletRed") *this = Color(219, 112, 147); + if(key == "maroon") *this = Color(176, 48, 96); + if(key == "x11 maroon") *this = Color(176, 48, 96); + if(key == "X11Maroon") *this = Color(176, 48, 96); + if(key == "web maroon") *this = Color(128, 0, 0); + if(key == "WebMaroon") *this = Color(128, 0, 0); + if(key == "medium violet red") *this = Color(199, 21, 133); + if(key == "MediumVioletRed") *this = Color(199, 21, 133); + if(key == "violet red") *this = Color(208, 32, 144); + if(key == "VioletRed") *this = Color(208, 32, 144); + if(key == "magenta") *this = Color(255, 0, 255); + if(key == "fuchsia") *this = Color(255, 0, 255); + if(key == "violet") *this = Color(238, 130, 238); + if(key == "plum") *this = Color(221, 160, 221); + if(key == "orchid") *this = Color(218, 112, 214); + if(key == "medium orchid") *this = Color(186, 85, 211); + if(key == "MediumOrchid") *this = Color(186, 85, 211); + if(key == "dark orchid") *this = Color(153, 50, 204); + if(key == "DarkOrchid") *this = Color(153, 50, 204); + if(key == "dark violet") *this = Color(148, 0, 211); + if(key == "DarkViolet") *this = Color(148, 0, 211); + if(key == "blue violet") *this = Color(138, 43, 226); + if(key == "BlueViolet") *this = Color(138, 43, 226); + if(key == "purple") *this = Color(160, 32, 240); + if(key == "x11 purple") *this = Color(160, 32, 240); + if(key == "X11Purple") *this = Color(160, 32, 240); + if(key == "web purple") *this = Color(128, 0, 128); + if(key == "WebPurple") *this = Color(128, 0, 128); + if(key == "medium purple") *this = Color(147, 112, 219); + if(key == "MediumPurple") *this = Color(147, 112, 219); + if(key == "thistle") *this = Color(216, 191, 216); + if(key == "snow1") *this = Color(255, 250, 250); + if(key == "snow2") *this = Color(238, 233, 233); + if(key == "snow3") *this = Color(205, 201, 201); + if(key == "snow4") *this = Color(139, 137, 137); + if(key == "seashell1") *this = Color(255, 245, 238); + if(key == "seashell2") *this = Color(238, 229, 222); + if(key == "seashell3") *this = Color(205, 197, 191); + if(key == "seashell4") *this = Color(139, 134, 130); + if(key == "AntiqueWhite1") *this = Color(255, 239, 219); + if(key == "AntiqueWhite2") *this = Color(238, 223, 204); + if(key == "AntiqueWhite3") *this = Color(205, 192, 176); + if(key == "AntiqueWhite4") *this = Color(139, 131, 120); + if(key == "bisque1") *this = Color(255, 228, 196); + if(key == "bisque2") *this = Color(238, 213, 183); + if(key == "bisque3") *this = Color(205, 183, 158); + if(key == "bisque4") *this = Color(139, 125, 107); + if(key == "PeachPuff1") *this = Color(255, 218, 185); + if(key == "PeachPuff2") *this = Color(238, 203, 173); + if(key == "PeachPuff3") *this = Color(205, 175, 149); + if(key == "PeachPuff4") *this = Color(139, 119, 101); + if(key == "NavajoWhite1") *this = Color(255, 222, 173); + if(key == "NavajoWhite2") *this = Color(238, 207, 161); + if(key == "NavajoWhite3") *this = Color(205, 179, 139); + if(key == "NavajoWhite4") *this = Color(139, 121, 94); + if(key == "LemonChiffon1") *this = Color(255, 250, 205); + if(key == "LemonChiffon2") *this = Color(238, 233, 191); + if(key == "LemonChiffon3") *this = Color(205, 201, 165); + if(key == "LemonChiffon4") *this = Color(139, 137, 112); + if(key == "cornsilk1") *this = Color(255, 248, 220); + if(key == "cornsilk2") *this = Color(238, 232, 205); + if(key == "cornsilk3") *this = Color(205, 200, 177); + if(key == "cornsilk4") *this = Color(139, 136, 120); + if(key == "ivory1") *this = Color(255, 255, 240); + if(key == "ivory2") *this = Color(238, 238, 224); + if(key == "ivory3") *this = Color(205, 205, 193); + if(key == "ivory4") *this = Color(139, 139, 131); + if(key == "honeydew1") *this = Color(240, 255, 240); + if(key == "honeydew2") *this = Color(224, 238, 224); + if(key == "honeydew3") *this = Color(193, 205, 193); + if(key == "honeydew4") *this = Color(131, 139, 131); + if(key == "LavenderBlush1") *this = Color(255, 240, 245); + if(key == "LavenderBlush2") *this = Color(238, 224, 229); + if(key == "LavenderBlush3") *this = Color(205, 193, 197); + if(key == "LavenderBlush4") *this = Color(139, 131, 134); + if(key == "MistyRose1") *this = Color(255, 228, 225); + if(key == "MistyRose2") *this = Color(238, 213, 210); + if(key == "MistyRose3") *this = Color(205, 183, 181); + if(key == "MistyRose4") *this = Color(139, 125, 123); + if(key == "azure1") *this = Color(240, 255, 255); + if(key == "azure2") *this = Color(224, 238, 238); + if(key == "azure3") *this = Color(193, 205, 205); + if(key == "azure4") *this = Color(131, 139, 139); + if(key == "SlateBlue1") *this = Color(131, 111, 255); + if(key == "SlateBlue2") *this = Color(122, 103, 238); + if(key == "SlateBlue3") *this = Color(105, 89, 205); + if(key == "SlateBlue4") *this = Color(71, 60, 139); + if(key == "RoyalBlue1") *this = Color(72, 118, 255); + if(key == "RoyalBlue2") *this = Color(67, 110, 238); + if(key == "RoyalBlue3") *this = Color(58, 95, 205); + if(key == "RoyalBlue4") *this = Color(39, 64, 139); + if(key == "blue1") *this = Color(0, 0, 255); + if(key == "blue2") *this = Color(0, 0, 238); + if(key == "blue3") *this = Color(0, 0, 205); + if(key == "blue4") *this = Color(0, 0, 139); + if(key == "DodgerBlue1") *this = Color(30, 144, 255); + if(key == "DodgerBlue2") *this = Color(28, 134, 238); + if(key == "DodgerBlue3") *this = Color(24, 116, 205); + if(key == "DodgerBlue4") *this = Color(16, 78, 139); + if(key == "SteelBlue1") *this = Color(99, 184, 255); + if(key == "SteelBlue2") *this = Color(92, 172, 238); + if(key == "SteelBlue3") *this = Color(79, 148, 205); + if(key == "SteelBlue4") *this = Color(54, 100, 139); + if(key == "DeepSkyBlue1") *this = Color(0, 191, 255); + if(key == "DeepSkyBlue2") *this = Color(0, 178, 238); + if(key == "DeepSkyBlue3") *this = Color(0, 154, 205); + if(key == "DeepSkyBlue4") *this = Color(0, 104, 139); + if(key == "SkyBlue1") *this = Color(135, 206, 255); + if(key == "SkyBlue2") *this = Color(126, 192, 238); + if(key == "SkyBlue3") *this = Color(108, 166, 205); + if(key == "SkyBlue4") *this = Color(74, 112, 139); + if(key == "LightSkyBlue1") *this = Color(176, 226, 255); + if(key == "LightSkyBlue2") *this = Color(164, 211, 238); + if(key == "LightSkyBlue3") *this = Color(141, 182, 205); + if(key == "LightSkyBlue4") *this = Color(96, 123, 139); + if(key == "SlateGray1") *this = Color(198, 226, 255); + if(key == "SlateGray2") *this = Color(185, 211, 238); + if(key == "SlateGray3") *this = Color(159, 182, 205); + if(key == "SlateGray4") *this = Color(108, 123, 139); + if(key == "LightSteelBlue1") *this = Color(202, 225, 255); + if(key == "LightSteelBlue2") *this = Color(188, 210, 238); + if(key == "LightSteelBlue3") *this = Color(162, 181, 205); + if(key == "LightSteelBlue4") *this = Color(110, 123, 139); + if(key == "LightBlue1") *this = Color(191, 239, 255); + if(key == "LightBlue2") *this = Color(178, 223, 238); + if(key == "LightBlue3") *this = Color(154, 192, 205); + if(key == "LightBlue4") *this = Color(104, 131, 139); + if(key == "LightCyan1") *this = Color(224, 255, 255); + if(key == "LightCyan2") *this = Color(209, 238, 238); + if(key == "LightCyan3") *this = Color(180, 205, 205); + if(key == "LightCyan4") *this = Color(122, 139, 139); + if(key == "PaleTurquoise1") *this = Color(187, 255, 255); + if(key == "PaleTurquoise2") *this = Color(174, 238, 238); + if(key == "PaleTurquoise3") *this = Color(150, 205, 205); + if(key == "PaleTurquoise4") *this = Color(102, 139, 139); + if(key == "CadetBlue1") *this = Color(152, 245, 255); + if(key == "CadetBlue2") *this = Color(142, 229, 238); + if(key == "CadetBlue3") *this = Color(122, 197, 205); + if(key == "CadetBlue4") *this = Color(83, 134, 139); + if(key == "turquoise1") *this = Color(0, 245, 255); + if(key == "turquoise2") *this = Color(0, 229, 238); + if(key == "turquoise3") *this = Color(0, 197, 205); + if(key == "turquoise4") *this = Color(0, 134, 139); + if(key == "cyan1") *this = Color(0, 255, 255); + if(key == "cyan2") *this = Color(0, 238, 238); + if(key == "cyan3") *this = Color(0, 205, 205); + if(key == "cyan4") *this = Color(0, 139, 139); + if(key == "DarkSlateGray1") *this = Color(151, 255, 255); + if(key == "DarkSlateGray2") *this = Color(141, 238, 238); + if(key == "DarkSlateGray3") *this = Color(121, 205, 205); + if(key == "DarkSlateGray4") *this = Color(82, 139, 139); + if(key == "aquamarine1") *this = Color(127, 255, 212); + if(key == "aquamarine2") *this = Color(118, 238, 198); + if(key == "aquamarine3") *this = Color(102, 205, 170); + if(key == "aquamarine4") *this = Color(69, 139, 116); + if(key == "DarkSeaGreen1") *this = Color(193, 255, 193); + if(key == "DarkSeaGreen2") *this = Color(180, 238, 180); + if(key == "DarkSeaGreen3") *this = Color(155, 205, 155); + if(key == "DarkSeaGreen4") *this = Color(105, 139, 105); + if(key == "SeaGreen1") *this = Color(84, 255, 159); + if(key == "SeaGreen2") *this = Color(78, 238, 148); + if(key == "SeaGreen3") *this = Color(67, 205, 128); + if(key == "SeaGreen4") *this = Color(46, 139, 87); + if(key == "PaleGreen1") *this = Color(154, 255, 154); + if(key == "PaleGreen2") *this = Color(144, 238, 144); + if(key == "PaleGreen3") *this = Color(124, 205, 124); + if(key == "PaleGreen4") *this = Color(84, 139, 84); + if(key == "SpringGreen1") *this = Color(0, 255, 127); + if(key == "SpringGreen2") *this = Color(0, 238, 118); + if(key == "SpringGreen3") *this = Color(0, 205, 102); + if(key == "SpringGreen4") *this = Color(0, 139, 69); + if(key == "green1") *this = Color(0, 255, 0); + if(key == "green2") *this = Color(0, 238, 0); + if(key == "green3") *this = Color(0, 205, 0); + if(key == "green4") *this = Color(0, 139, 0); + if(key == "chartreuse1") *this = Color(127, 255, 0); + if(key == "chartreuse2") *this = Color(118, 238, 0); + if(key == "chartreuse3") *this = Color(102, 205, 0); + if(key == "chartreuse4") *this = Color(69, 139, 0); + if(key == "OliveDrab1") *this = Color(192, 255, 62); + if(key == "OliveDrab2") *this = Color(179, 238, 58); + if(key == "OliveDrab3") *this = Color(154, 205, 50); + if(key == "OliveDrab4") *this = Color(105, 139, 34); + if(key == "DarkOliveGreen1") *this = Color(202, 255, 112); + if(key == "DarkOliveGreen2") *this = Color(188, 238, 104); + if(key == "DarkOliveGreen3") *this = Color(162, 205, 90); + if(key == "DarkOliveGreen4") *this = Color(110, 139, 61); + if(key == "khaki1") *this = Color(255, 246, 143); + if(key == "khaki2") *this = Color(238, 230, 133); + if(key == "khaki3") *this = Color(205, 198, 115); + if(key == "khaki4") *this = Color(139, 134, 78); + if(key == "LightGoldenrod1") *this = Color(255, 236, 139); + if(key == "LightGoldenrod2") *this = Color(238, 220, 130); + if(key == "LightGoldenrod3") *this = Color(205, 190, 112); + if(key == "LightGoldenrod4") *this = Color(139, 129, 76); + if(key == "LightYellow1") *this = Color(255, 255, 224); + if(key == "LightYellow2") *this = Color(238, 238, 209); + if(key == "LightYellow3") *this = Color(205, 205, 180); + if(key == "LightYellow4") *this = Color(139, 139, 122); + if(key == "yellow1") *this = Color(255, 255, 0); + if(key == "yellow2") *this = Color(238, 238, 0); + if(key == "yellow3") *this = Color(205, 205, 0); + if(key == "yellow4") *this = Color(139, 139, 0); + if(key == "gold1") *this = Color(255, 215, 0); + if(key == "gold2") *this = Color(238, 201, 0); + if(key == "gold3") *this = Color(205, 173, 0); + if(key == "gold4") *this = Color(139, 117, 0); + if(key == "goldenrod1") *this = Color(255, 193, 37); + if(key == "goldenrod2") *this = Color(238, 180, 34); + if(key == "goldenrod3") *this = Color(205, 155, 29); + if(key == "goldenrod4") *this = Color(139, 105, 20); + if(key == "DarkGoldenrod1") *this = Color(255, 185, 15); + if(key == "DarkGoldenrod2") *this = Color(238, 173, 14); + if(key == "DarkGoldenrod3") *this = Color(205, 149, 12); + if(key == "DarkGoldenrod4") *this = Color(139, 101, 8); + if(key == "RosyBrown1") *this = Color(255, 193, 193); + if(key == "RosyBrown2") *this = Color(238, 180, 180); + if(key == "RosyBrown3") *this = Color(205, 155, 155); + if(key == "RosyBrown4") *this = Color(139, 105, 105); + if(key == "IndianRed1") *this = Color(255, 106, 106); + if(key == "IndianRed2") *this = Color(238, 99, 99); + if(key == "IndianRed3") *this = Color(205, 85, 85); + if(key == "IndianRed4") *this = Color(139, 58, 58); + if(key == "sienna1") *this = Color(255, 130, 71); + if(key == "sienna2") *this = Color(238, 121, 66); + if(key == "sienna3") *this = Color(205, 104, 57); + if(key == "sienna4") *this = Color(139, 71, 38); + if(key == "burlywood1") *this = Color(255, 211, 155); + if(key == "burlywood2") *this = Color(238, 197, 145); + if(key == "burlywood3") *this = Color(205, 170, 125); + if(key == "burlywood4") *this = Color(139, 115, 85); + if(key == "wheat1") *this = Color(255, 231, 186); + if(key == "wheat2") *this = Color(238, 216, 174); + if(key == "wheat3") *this = Color(205, 186, 150); + if(key == "wheat4") *this = Color(139, 126, 102); + if(key == "tan1") *this = Color(255, 165, 79); + if(key == "tan2") *this = Color(238, 154, 73); + if(key == "tan3") *this = Color(205, 133, 63); + if(key == "tan4") *this = Color(139, 90, 43); + if(key == "chocolate1") *this = Color(255, 127, 36); + if(key == "chocolate2") *this = Color(238, 118, 33); + if(key == "chocolate3") *this = Color(205, 102, 29); + if(key == "chocolate4") *this = Color(139, 69, 19); + if(key == "firebrick1") *this = Color(255, 48, 48); + if(key == "firebrick2") *this = Color(238, 44, 44); + if(key == "firebrick3") *this = Color(205, 38, 38); + if(key == "firebrick4") *this = Color(139, 26, 26); + if(key == "brown1") *this = Color(255, 64, 64); + if(key == "brown2") *this = Color(238, 59, 59); + if(key == "brown3") *this = Color(205, 51, 51); + if(key == "brown4") *this = Color(139, 35, 35); + if(key == "salmon1") *this = Color(255, 140, 105); + if(key == "salmon2") *this = Color(238, 130, 98); + if(key == "salmon3") *this = Color(205, 112, 84); + if(key == "salmon4") *this = Color(139, 76, 57); + if(key == "LightSalmon1") *this = Color(255, 160, 122); + if(key == "LightSalmon2") *this = Color(238, 149, 114); + if(key == "LightSalmon3") *this = Color(205, 129, 98); + if(key == "LightSalmon4") *this = Color(139, 87, 66); + if(key == "orange1") *this = Color(255, 165, 0); + if(key == "orange2") *this = Color(238, 154, 0); + if(key == "orange3") *this = Color(205, 133, 0); + if(key == "orange4") *this = Color(139, 90, 0); + if(key == "DarkOrange1") *this = Color(255, 127, 0); + if(key == "DarkOrange2") *this = Color(238, 118, 0); + if(key == "DarkOrange3") *this = Color(205, 102, 0); + if(key == "DarkOrange4") *this = Color(139, 69, 0); + if(key == "coral1") *this = Color(255, 114, 86); + if(key == "coral2") *this = Color(238, 106, 80); + if(key == "coral3") *this = Color(205, 91, 69); + if(key == "coral4") *this = Color(139, 62, 47); + if(key == "tomato1") *this = Color(255, 99, 71); + if(key == "tomato2") *this = Color(238, 92, 66); + if(key == "tomato3") *this = Color(205, 79, 57); + if(key == "tomato4") *this = Color(139, 54, 38); + if(key == "OrangeRed1") *this = Color(255, 69, 0); + if(key == "OrangeRed2") *this = Color(238, 64, 0); + if(key == "OrangeRed3") *this = Color(205, 55, 0); + if(key == "OrangeRed4") *this = Color(139, 37, 0); + if(key == "red1") *this = Color(255, 0, 0); + if(key == "red2") *this = Color(238, 0, 0); + if(key == "red3") *this = Color(205, 0, 0); + if(key == "red4") *this = Color(139, 0, 0); + if(key == "DeepPink1") *this = Color(255, 20, 147); + if(key == "DeepPink2") *this = Color(238, 18, 137); + if(key == "DeepPink3") *this = Color(205, 16, 118); + if(key == "DeepPink4") *this = Color(139, 10, 80); + if(key == "HotPink1") *this = Color(255, 110, 180); + if(key == "HotPink2") *this = Color(238, 106, 167); + if(key == "HotPink3") *this = Color(205, 96, 144); + if(key == "HotPink4") *this = Color(139, 58, 98); + if(key == "pink1") *this = Color(255, 181, 197); + if(key == "pink2") *this = Color(238, 169, 184); + if(key == "pink3") *this = Color(205, 145, 158); + if(key == "pink4") *this = Color(139, 99, 108); + if(key == "LightPink1") *this = Color(255, 174, 185); + if(key == "LightPink2") *this = Color(238, 162, 173); + if(key == "LightPink3") *this = Color(205, 140, 149); + if(key == "LightPink4") *this = Color(139, 95, 101); + if(key == "PaleVioletRed1") *this = Color(255, 130, 171); + if(key == "PaleVioletRed2") *this = Color(238, 121, 159); + if(key == "PaleVioletRed3") *this = Color(205, 104, 137); + if(key == "PaleVioletRed4") *this = Color(139, 71, 93); + if(key == "maroon1") *this = Color(255, 52, 179); + if(key == "maroon2") *this = Color(238, 48, 167); + if(key == "maroon3") *this = Color(205, 41, 144); + if(key == "maroon4") *this = Color(139, 28, 98); + if(key == "VioletRed1") *this = Color(255, 62, 150); + if(key == "VioletRed2") *this = Color(238, 58, 140); + if(key == "VioletRed3") *this = Color(205, 50, 120); + if(key == "VioletRed4") *this = Color(139, 34, 82); + if(key == "magenta1") *this = Color(255, 0, 255); + if(key == "magenta2") *this = Color(238, 0, 238); + if(key == "magenta3") *this = Color(205, 0, 205); + if(key == "magenta4") *this = Color(139, 0, 139); + if(key == "orchid1") *this = Color(255, 131, 250); + if(key == "orchid2") *this = Color(238, 122, 233); + if(key == "orchid3") *this = Color(205, 105, 201); + if(key == "orchid4") *this = Color(139, 71, 137); + if(key == "plum1") *this = Color(255, 187, 255); + if(key == "plum2") *this = Color(238, 174, 238); + if(key == "plum3") *this = Color(205, 150, 205); + if(key == "plum4") *this = Color(139, 102, 139); + if(key == "MediumOrchid1") *this = Color(224, 102, 255); + if(key == "MediumOrchid2") *this = Color(209, 95, 238); + if(key == "MediumOrchid3") *this = Color(180, 82, 205); + if(key == "MediumOrchid4") *this = Color(122, 55, 139); + if(key == "DarkOrchid1") *this = Color(191, 62, 255); + if(key == "DarkOrchid2") *this = Color(178, 58, 238); + if(key == "DarkOrchid3") *this = Color(154, 50, 205); + if(key == "DarkOrchid4") *this = Color(104, 34, 139); + if(key == "purple1") *this = Color(155, 48, 255); + if(key == "purple2") *this = Color(145, 44, 238); + if(key == "purple3") *this = Color(125, 38, 205); + if(key == "purple4") *this = Color(85, 26, 139); + if(key == "MediumPurple1") *this = Color(171, 130, 255); + if(key == "MediumPurple2") *this = Color(159, 121, 238); + if(key == "MediumPurple3") *this = Color(137, 104, 205); + if(key == "MediumPurple4") *this = Color(93, 71, 139); + if(key == "thistle1") *this = Color(255, 225, 255); + if(key == "thistle2") *this = Color(238, 210, 238); + if(key == "thistle3") *this = Color(205, 181, 205); + if(key == "thistle4") *this = Color(139, 123, 139); + if(key == "gray0") *this = Color(0, 0, 0); + if(key == "grey0") *this = Color(0, 0, 0); + if(key == "gray1") *this = Color(3, 3, 3); + if(key == "grey1") *this = Color(3, 3, 3); + if(key == "gray2") *this = Color(5, 5, 5); + if(key == "grey2") *this = Color(5, 5, 5); + if(key == "gray3") *this = Color(8, 8, 8); + if(key == "grey3") *this = Color(8, 8, 8); + if(key == "gray4") *this = Color(10, 10, 10); + if(key == "grey4") *this = Color(10, 10, 10); + if(key == "gray5") *this = Color(13, 13, 13); + if(key == "grey5") *this = Color(13, 13, 13); + if(key == "gray6") *this = Color(15, 15, 15); + if(key == "grey6") *this = Color(15, 15, 15); + if(key == "gray7") *this = Color(18, 18, 18); + if(key == "grey7") *this = Color(18, 18, 18); + if(key == "gray8") *this = Color(20, 20, 20); + if(key == "grey8") *this = Color(20, 20, 20); + if(key == "gray9") *this = Color(23, 23, 23); + if(key == "grey9") *this = Color(23, 23, 23); + if(key == "gray10") *this = Color(26, 26, 26); + if(key == "grey10") *this = Color(26, 26, 26); + if(key == "gray11") *this = Color(28, 28, 28); + if(key == "grey11") *this = Color(28, 28, 28); + if(key == "gray12") *this = Color(31, 31, 31); + if(key == "grey12") *this = Color(31, 31, 31); + if(key == "gray13") *this = Color(33, 33, 33); + if(key == "grey13") *this = Color(33, 33, 33); + if(key == "gray14") *this = Color(36, 36, 36); + if(key == "grey14") *this = Color(36, 36, 36); + if(key == "gray15") *this = Color(38, 38, 38); + if(key == "grey15") *this = Color(38, 38, 38); + if(key == "gray16") *this = Color(41, 41, 41); + if(key == "grey16") *this = Color(41, 41, 41); + if(key == "gray17") *this = Color(43, 43, 43); + if(key == "grey17") *this = Color(43, 43, 43); + if(key == "gray18") *this = Color(46, 46, 46); + if(key == "grey18") *this = Color(46, 46, 46); + if(key == "gray19") *this = Color(48, 48, 48); + if(key == "grey19") *this = Color(48, 48, 48); + if(key == "gray20") *this = Color(51, 51, 51); + if(key == "grey20") *this = Color(51, 51, 51); + if(key == "gray21") *this = Color(54, 54, 54); + if(key == "grey21") *this = Color(54, 54, 54); + if(key == "gray22") *this = Color(56, 56, 56); + if(key == "grey22") *this = Color(56, 56, 56); + if(key == "gray23") *this = Color(59, 59, 59); + if(key == "grey23") *this = Color(59, 59, 59); + if(key == "gray24") *this = Color(61, 61, 61); + if(key == "grey24") *this = Color(61, 61, 61); + if(key == "gray25") *this = Color(64, 64, 64); + if(key == "grey25") *this = Color(64, 64, 64); + if(key == "gray26") *this = Color(66, 66, 66); + if(key == "grey26") *this = Color(66, 66, 66); + if(key == "gray27") *this = Color(69, 69, 69); + if(key == "grey27") *this = Color(69, 69, 69); + if(key == "gray28") *this = Color(71, 71, 71); + if(key == "grey28") *this = Color(71, 71, 71); + if(key == "gray29") *this = Color(74, 74, 74); + if(key == "grey29") *this = Color(74, 74, 74); + if(key == "gray30") *this = Color(77, 77, 77); + if(key == "grey30") *this = Color(77, 77, 77); + if(key == "gray31") *this = Color(79, 79, 79); + if(key == "grey31") *this = Color(79, 79, 79); + if(key == "gray32") *this = Color(82, 82, 82); + if(key == "grey32") *this = Color(82, 82, 82); + if(key == "gray33") *this = Color(84, 84, 84); + if(key == "grey33") *this = Color(84, 84, 84); + if(key == "gray34") *this = Color(87, 87, 87); + if(key == "grey34") *this = Color(87, 87, 87); + if(key == "gray35") *this = Color(89, 89, 89); + if(key == "grey35") *this = Color(89, 89, 89); + if(key == "gray36") *this = Color(92, 92, 92); + if(key == "grey36") *this = Color(92, 92, 92); + if(key == "gray37") *this = Color(94, 94, 94); + if(key == "grey37") *this = Color(94, 94, 94); + if(key == "gray38") *this = Color(97, 97, 97); + if(key == "grey38") *this = Color(97, 97, 97); + if(key == "gray39") *this = Color(99, 99, 99); + if(key == "grey39") *this = Color(99, 99, 99); + if(key == "gray40") *this = Color(102, 102, 102); + if(key == "grey40") *this = Color(102, 102, 102); + if(key == "gray41") *this = Color(105, 105, 105); + if(key == "grey41") *this = Color(105, 105, 105); + if(key == "gray42") *this = Color(107, 107, 107); + if(key == "grey42") *this = Color(107, 107, 107); + if(key == "gray43") *this = Color(110, 110, 110); + if(key == "grey43") *this = Color(110, 110, 110); + if(key == "gray44") *this = Color(112, 112, 112); + if(key == "grey44") *this = Color(112, 112, 112); + if(key == "gray45") *this = Color(115, 115, 115); + if(key == "grey45") *this = Color(115, 115, 115); + if(key == "gray46") *this = Color(117, 117, 117); + if(key == "grey46") *this = Color(117, 117, 117); + if(key == "gray47") *this = Color(120, 120, 120); + if(key == "grey47") *this = Color(120, 120, 120); + if(key == "gray48") *this = Color(122, 122, 122); + if(key == "grey48") *this = Color(122, 122, 122); + if(key == "gray49") *this = Color(125, 125, 125); + if(key == "grey49") *this = Color(125, 125, 125); + if(key == "gray50") *this = Color(127, 127, 127); + if(key == "grey50") *this = Color(127, 127, 127); + if(key == "gray51") *this = Color(130, 130, 130); + if(key == "grey51") *this = Color(130, 130, 130); + if(key == "gray52") *this = Color(133, 133, 133); + if(key == "grey52") *this = Color(133, 133, 133); + if(key == "gray53") *this = Color(135, 135, 135); + if(key == "grey53") *this = Color(135, 135, 135); + if(key == "gray54") *this = Color(138, 138, 138); + if(key == "grey54") *this = Color(138, 138, 138); + if(key == "gray55") *this = Color(140, 140, 140); + if(key == "grey55") *this = Color(140, 140, 140); + if(key == "gray56") *this = Color(143, 143, 143); + if(key == "grey56") *this = Color(143, 143, 143); + if(key == "gray57") *this = Color(145, 145, 145); + if(key == "grey57") *this = Color(145, 145, 145); + if(key == "gray58") *this = Color(148, 148, 148); + if(key == "grey58") *this = Color(148, 148, 148); + if(key == "gray59") *this = Color(150, 150, 150); + if(key == "grey59") *this = Color(150, 150, 150); + if(key == "gray60") *this = Color(153, 153, 153); + if(key == "grey60") *this = Color(153, 153, 153); + if(key == "gray61") *this = Color(156, 156, 156); + if(key == "grey61") *this = Color(156, 156, 156); + if(key == "gray62") *this = Color(158, 158, 158); + if(key == "grey62") *this = Color(158, 158, 158); + if(key == "gray63") *this = Color(161, 161, 161); + if(key == "grey63") *this = Color(161, 161, 161); + if(key == "gray64") *this = Color(163, 163, 163); + if(key == "grey64") *this = Color(163, 163, 163); + if(key == "gray65") *this = Color(166, 166, 166); + if(key == "grey65") *this = Color(166, 166, 166); + if(key == "gray66") *this = Color(168, 168, 168); + if(key == "grey66") *this = Color(168, 168, 168); + if(key == "gray67") *this = Color(171, 171, 171); + if(key == "grey67") *this = Color(171, 171, 171); + if(key == "gray68") *this = Color(173, 173, 173); + if(key == "grey68") *this = Color(173, 173, 173); + if(key == "gray69") *this = Color(176, 176, 176); + if(key == "grey69") *this = Color(176, 176, 176); + if(key == "gray70") *this = Color(179, 179, 179); + if(key == "grey70") *this = Color(179, 179, 179); + if(key == "gray71") *this = Color(181, 181, 181); + if(key == "grey71") *this = Color(181, 181, 181); + if(key == "gray72") *this = Color(184, 184, 184); + if(key == "grey72") *this = Color(184, 184, 184); + if(key == "gray73") *this = Color(186, 186, 186); + if(key == "grey73") *this = Color(186, 186, 186); + if(key == "gray74") *this = Color(189, 189, 189); + if(key == "grey74") *this = Color(189, 189, 189); + if(key == "gray75") *this = Color(191, 191, 191); + if(key == "grey75") *this = Color(191, 191, 191); + if(key == "gray76") *this = Color(194, 194, 194); + if(key == "grey76") *this = Color(194, 194, 194); + if(key == "gray77") *this = Color(196, 196, 196); + if(key == "grey77") *this = Color(196, 196, 196); + if(key == "gray78") *this = Color(199, 199, 199); + if(key == "grey78") *this = Color(199, 199, 199); + if(key == "gray79") *this = Color(201, 201, 201); + if(key == "grey79") *this = Color(201, 201, 201); + if(key == "gray80") *this = Color(204, 204, 204); + if(key == "grey80") *this = Color(204, 204, 204); + if(key == "gray81") *this = Color(207, 207, 207); + if(key == "grey81") *this = Color(207, 207, 207); + if(key == "gray82") *this = Color(209, 209, 209); + if(key == "grey82") *this = Color(209, 209, 209); + if(key == "gray83") *this = Color(212, 212, 212); + if(key == "grey83") *this = Color(212, 212, 212); + if(key == "gray84") *this = Color(214, 214, 214); + if(key == "grey84") *this = Color(214, 214, 214); + if(key == "gray85") *this = Color(217, 217, 217); + if(key == "grey85") *this = Color(217, 217, 217); + if(key == "gray86") *this = Color(219, 219, 219); + if(key == "grey86") *this = Color(219, 219, 219); + if(key == "gray87") *this = Color(222, 222, 222); + if(key == "grey87") *this = Color(222, 222, 222); + if(key == "gray88") *this = Color(224, 224, 224); + if(key == "grey88") *this = Color(224, 224, 224); + if(key == "gray89") *this = Color(227, 227, 227); + if(key == "grey89") *this = Color(227, 227, 227); + if(key == "gray90") *this = Color(229, 229, 229); + if(key == "grey90") *this = Color(229, 229, 229); + if(key == "gray91") *this = Color(232, 232, 232); + if(key == "grey91") *this = Color(232, 232, 232); + if(key == "gray92") *this = Color(235, 235, 235); + if(key == "grey92") *this = Color(235, 235, 235); + if(key == "gray93") *this = Color(237, 237, 237); + if(key == "grey93") *this = Color(237, 237, 237); + if(key == "gray94") *this = Color(240, 240, 240); + if(key == "grey94") *this = Color(240, 240, 240); + if(key == "gray95") *this = Color(242, 242, 242); + if(key == "grey95") *this = Color(242, 242, 242); + if(key == "gray96") *this = Color(245, 245, 245); + if(key == "grey96") *this = Color(245, 245, 245); + if(key == "gray97") *this = Color(247, 247, 247); + if(key == "grey97") *this = Color(247, 247, 247); + if(key == "gray98") *this = Color(250, 250, 250); + if(key == "grey98") *this = Color(250, 250, 250); + if(key == "gray99") *this = Color(252, 252, 252); + if(key == "grey99") *this = Color(252, 252, 252); + if(key == "gray100") *this = Color(255, 255, 255); + if(key == "grey100") *this = Color(255, 255, 255); + if(key == "dark grey") *this = Color(169, 169, 169); + if(key == "DarkGrey") *this = Color(169, 169, 169); + if(key == "dark gray") *this = Color(169, 169, 169); + if(key == "DarkGray") *this = Color(169, 169, 169); + if(key == "dark blue") *this = Color(0, 0, 139); + if(key == "DarkBlue") *this = Color(0, 0, 139); + if(key == "dark cyan") *this = Color(0, 139, 139); + if(key == "DarkCyan") *this = Color(0, 139, 139); + if(key == "dark magenta") *this = Color(139, 0, 139); + if(key == "DarkMagenta") *this = Color(139, 0, 139); + if(key == "dark red") *this = Color(139, 0, 0); + if(key == "DarkRed") *this = Color(139, 0, 0); + if(key == "light green") *this = Color(144, 238, 144); + if(key == "LightGreen") *this = Color(144, 238, 144); + if(key == "crimson") *this = Color(220, 20, 60); + if(key == "indigo") *this = Color(75, 0, 130); + if(key == "olive") *this = Color(128, 128, 0); + if(key == "rebecca purple") *this = Color(102, 51, 153); + if(key == "RebeccaPurple") *this = Color(102, 51, 153); + if(key == "silver") *this = Color(192, 192, 192); + if(key == "teal") *this = Color(0, 128, 128); +} diff --git a/src/color.h b/src/color.h new file mode 100644 index 0000000..1e8ac1a --- /dev/null +++ b/src/color.h @@ -0,0 +1,50 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef COLOR_H +#define COLOR_H + +#include +#include + +#include "rustutils/lexcompare.h" + +namespace IVD +{ + + +struct Color +{ + //For "DeepColor", probably should just switch channel + // to floating point so the (0,255, 5), #ffffff, etc + // stuff translates easily. Could also be rounded off + // for legacy hardware. + typedef uint8_t Channel; + typedef Channel AlphaType; + + Channel red; + Channel green; + Channel blue; + + std::string generateHexPrint() const; + std::string generateDecPrint() const; + + Color() {} + Color(Channel r, Channel g, Channel b): + red(r), green(g), blue(b) + {} + Color(const std::string& key); + + RUSTUTILS_DEFINE_COMP(Color, red, green, blue) +}; + +struct GradientColorStop +{ + Color myColor; + Color::AlphaType myAlpha; + double pos; +}; + + +}//IVD + +#endif // COLOR_H diff --git a/src/compiler.cpp b/src/compiler.cpp new file mode 100755 index 0000000..11fc594 --- /dev/null +++ b/src/compiler.cpp @@ -0,0 +1,2042 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "compiler.h" + +#include +#include +#include + +#include "rustutils/routine.h" + +#include "keywords.h" +#include "codeposition.h" + + +namespace IVD +{ + +std::string SyntaxError::printout(const char* context) const +{ + const int terminalWidth = 80; + const int maxArrowLineWidth = terminalWidth / 2; + + auto getPrettyPositionReadout = [&](const CodePosition pos) + { + const char* p = context; + + const int columnSize = pos.column + 1; + const int frontTruncation = columnSize > maxArrowLineWidth ? columnSize - maxArrowLineWidth + : 0; + + const int arrowPos = frontTruncation ? maxArrowLineWidth - 1 + : pos.column; + + for(int lineCount = 0; *p && lineCount != pos.line; ++p) if(*p == '\n') ++lineCount; + + std::string codeLine; + for(; *p && *p != '\n'; ++p) codeLine += *p; + + if(frontTruncation) + { + codeLine = std::string(codeLine.begin() + frontTruncation, codeLine.end()); + for(int i = 0; i != 3; ++i) codeLine[i] = '.'; + } + + if(codeLine.size() > terminalWidth) + { + codeLine = std::string(codeLine.begin(), codeLine.begin() + terminalWidth - 1); + + int i = 0; + for(auto rit = codeLine.rbegin(); i != 3; ++i) *rit = '.'; + } + + std::string arrowLine; + while(arrowLine.size() != arrowPos) arrowLine += ' '; + arrowLine += '^'; + + return codeLine + '\n' + arrowLine + '\n'; + }; + + + std::stringstream myErrStream; + + //Line numbers are 1 based smfh... + myErrStream << "Syntax error on line " << pos.line + 1 << ":" << std::endl + << getPrettyPositionReadout(pos); + + if(expecting) + { + auto filterExpecting = [&](const int sym) + { + //This is for _any_ type that isn't really a type... + //no idea what the above comment means ~ Tracy feb 2021 + //But this switch does seem incomplete. + switch(sym) + { + case Keyword::UserToken: return "User Token"; + case Keyword::ScalarType: return "Number"; + case Keyword::FloatType: return "Float"; + default: return "getLiteralForSymbol(sym) TODO LOLOL"; + } + }; + + myErrStream << "Was expecting: \"" << filterExpecting(expecting->expected) + << "\", got: \"" << filterExpecting(expecting->got) << "\"" << std::endl; + } + else if(errStr && conflictsWith) + { + myErrStream << *errStr << " conflicts with previous definition on line: " + << conflictsWith->line + 1 << ":" << std::endl + << getPrettyPositionReadout(*conflictsWith); + } + else if(errStr) + { + myErrStream << *errStr << std::endl; + } + + return myErrStream.str(); +} + +std::vector Compiler::tokenizeInput(const std::string code) +{ + auto pos = code.begin(); + + + CodePosition currentPos{0, 0}; + + + auto notEnd = [&] + { return pos != code.end(); }; + + auto reachedEnd = [&] + { return !notEnd(); }; + + auto checkCurrentIsWhitespace = [&]() -> bool + { return notEnd() && (*pos == '\r' || *pos == '\n' || *pos == '\t' || *pos == '\f' || *pos == '\v' || *pos == ' '); }; + + auto nextChar = [&]() + { + if(!notEnd()) return false; //If not not end? Why not not? + + if(*pos == '\n') + { + while(notEnd() && *pos == '\n') + { + ++pos; + ++currentPos.line; + currentPos.column = 0; + } + } + else + { + ++currentPos.column; + ++pos; + } + + return notEnd(); + }; + + auto checkNextIsEq = [&](const char myChar) -> bool + { + return notEnd() && pos + 1 != code.end() && *(pos + 1) == myChar; + }; + + auto compareStringAtPos = [&](const std::string myString) -> bool + { + if(reachedEnd()) return false; + + auto ipos = pos; + auto spos = myString.begin(); + while(ipos != code.end() && spos != myString.end()) + { + if(*ipos != *spos) return false; + + ++ipos; + ++spos; + } + return spos == myString.end(); + }; + + std::vector myTokens; + + + //TODO it's inconsistent here. Some matchers store the starting code positions + // and this first one just creates a token off the bat, which gets the current position, + // which I believe to be more elegant... TODO + + auto matchLiteral = [&]() + { + if(reachedEnd()) return false; + + if(*pos == '"') + { + Token myToken(currentPos, Keyword::UserString); + + while(nextChar()) + { + if(*pos == '\\' && checkNextIsEq('"')) + { + nextChar(); //Eat the slash, the loop eats the quote. + continue; + } + + if(*pos == '"') + { + nextChar(); + break; + } + + myToken.userString += *pos; + } + + if(myToken.userString.size()) //We ignore empty strings, apparently. + myTokens.push_back(myToken); + + return true; + } + return false; + }; + + auto matchWhitespace = [&] + { + if(!checkCurrentIsWhitespace()) return false; + while(nextChar() && checkCurrentIsWhitespace()); + return true; + }; + + auto matchComment = [&]() + { + if(reachedEnd()) return false; + + if(*pos == '/' && checkNextIsEq('/')) + { + while(nextChar() && *pos != '\n'); + return true; + } + return false; + }; + + auto matchColor = [&]() + { + if(reachedEnd()) return false; + + if(*pos != '#') return false; + + const CodePosition startPos = currentPos; + + std::string hexNumber; + while(nextChar() && std::isxdigit(*pos)) + hexNumber += *pos; + + if(pos == code.end() || hexNumber.size() != 6) return false; + + Color theColor; + + auto it = hexNumber.begin(); + + theColor.red = std::stoi(std::string(it, it + 2), 0, 16); + it += 2; + theColor.green = std::stoi(std::string(it, it + 2), 0, 16); + it += 2; + theColor.blue = std::stoi(std::string(it, it + 2), 0, 16); + + Token myToken(startPos, Keyword::ColorLiteral); + myToken.userColor = theColor; + + myTokens.push_back(myToken); + + return true; + }; + + auto matchDelimitingSymbol = [&]() + { + if(reachedEnd()) return false; + + std::optional symbol; + + const CodePosition startPos = currentPos; + + if(*pos == '-' && checkNextIsEq('>')) + { + symbol = Keyword::Arrow; + nextChar(); //Eats an extra one + } + else symbol = getSymbolForLiteral(std::string({*pos})); + + if(symbol && checkIsDelimitingSymbol(*symbol)) + { + myTokens.emplace_back(startPos, *symbol); + nextChar(); + return true; + } + return false; + }; + + auto matchNumber = [&]() + { + if(reachedEnd()) return false; + + std::string number; //Yeah, that's not confusing at all + + const CodePosition startPos = currentPos; + + auto consumeDigits = [&] + { + bool worked = false; + for(; notEnd() && std::isdigit(*pos); nextChar()) + { + number += *pos; + worked = true; + } + return worked; + }; + + if(!consumeDigits()) return false; + + if(notEnd() && *pos == '.') + { + //Not sure if this is strictly necessary, but I'm worried + // starting with just the floating point might conflict with + // locale rules... But thats questionable with "." too? TODO + if(number.empty()) number += '0'; + + number += '.'; //stod needs this. + nextChar(); //Eat the dot... + + if(!consumeDigits()) return false; + + Token myToken(startPos, Keyword::FloatType); + myToken.userNumber = std::stod(number); + myTokens.push_back(myToken); + return true; + } + else if(number.size()) + { + Token myToken(startPos, Keyword::ScalarType); + myToken.userNumber = std::stoi(number); + myTokens.push_back(myToken); + return true; + } + return false; + }; + + auto matchToken = [&]() + { + if(reachedEnd()) return false; + + const CodePosition tokenStartPosition = currentPos; + + std::string tokenString; + for(; notEnd(); nextChar()) + { + auto optionalLiteral = getSymbolForLiteral({*pos}); + if(optionalLiteral && checkIsDelimitingSymbol(*optionalLiteral)) + break; + + //God how is this so much simpler than my last approach?? + if(*pos == '-' && checkNextIsEq('>')) break; + + //Ehhh... I don't want slashes in character names, they make even less sense than + // +... But... Consistency... Aghh... + if(*pos == '/' && checkNextIsEq('/')) break; + + if(checkCurrentIsWhitespace()) break; + + tokenString += *pos; + } + + if(tokenString.size()) + { + //Check whether the token is a reserved keyword or not. + const auto optional = getSymbolForLiteral(tokenString); + + if(optional) + myTokens.emplace_back(tokenStartPosition, *optional); + else + { + Token myToken(tokenStartPosition, Keyword::UserToken); + myToken.userToken = tokenString; + myTokens.push_back(myToken); + } + + return true; + } + return false; + }; + + + for(; notEnd();) + { + auto filter = [&](auto fun) + { + auto savedPos = pos; + auto savedCodePos = currentPos; + if(!fun()) + { + pos = savedPos; + currentPos = savedCodePos; + return false; + } + return true; + }; + + //Order here is absolutely arbitrary(?), but it was working before so eh TODO + if(!filter(matchLiteral) && + !filter(matchWhitespace) && + !filter(matchComment) && + !filter(matchColor) && + !filter(matchDelimitingSymbol) && + !filter(matchNumber) && + !filter(matchToken)) + { break; } + } + + assert(pos == code.end()); + + return myTokens; +} + +std::vector Compiler::parseTokens(std::vector tokens) +{ + auto parserPos = tokens.begin(); + std::vector precursors; + + auto reachedEnd = [&]() { return parserPos == tokens.end(); }; + + auto getCurrentToken = [&] + { + if(reachedEnd()) throw(UnexpectedEOF()); + return *parserPos; + }; + + auto matchSymbolSomft = [&](const int symbol) + { + if(reachedEnd()) return false; + return parserPos->symbol == symbol; + }; + + auto matchSymbolHard = [&](const int symbol) + { + if(parserPos->symbol != symbol) throw SyntaxError(getCurrentToken(), symbol); + }; + + auto next = [&] + { + if(reachedEnd()) return false; + ++parserPos; + return !reachedEnd(); + }; + + auto nextHard = [&]() + { + if(!next()) + throw UnexpectedEOF(); + }; + + auto matchNextAdvance = [&](const int symbol) + { + nextHard(); + matchSymbolHard(symbol); + }; + + auto peekSymbolSequence = [&](const std::vector symbols) + { + auto iit = symbols.begin(); + auto sit = parserPos; + + while(iit != symbols.end() && sit != tokens.end()) + { + if(*iit != sit->symbol) return false; + ++iit; + ++sit; + } + return true; + }; + + auto advanceTo = [&](const std::vector delimiters) + { + //TODO definitely print where we currently are, and also maybe say "advancing to line/column" ? :) + + while(next()) for(const int del : delimiters) if(matchSymbolSomft(del)) return; + }; + + + //Factory function becauseeeeeee it seems to change all the time + auto allocateElementPrecursor = [&]() + { + return ElementPrecursor(getCurrentToken().codePosition, + getFreshElementStamp(), + attrKeyToBodyTypeMap.size()); + }; + + + + //-----------------Let the parsing e X t R a V a G a N z A BEGIN!------------------------- + //Free defines are limited in scope to the current translation unit. Should be easy + // enough to add an "export" function if need be. + ScopedVariables varsForTranslationUnit; + + std::function parseExpression; + + auto parseValueKeyScopeAndPath = [&] + { + //This function. On purpose. Ignores a single token. + //This is because the rules for how to deal with a single one vary + //based on what it is for. Expression value keys need the single one + // to be the key, others need it to be part of the path. The user is expected + // to handle this case. + ScopedValueKey result; + result.definedAt = getCurrentToken().codePosition; + + if(peekSymbolSequence({Keyword::UserToken, Keyword::Dot}) || + peekSymbolSequence({Keyword::UserToken, Keyword::Colon, Keyword::Colon})) + { + //Where else would it point to? + result.myScope = ScopedValueKey::Scope::Global; + result.addToPath(getCurrentToken().userToken); + + //userToken (:: | .) //That's... a lot of nipples... + nextHard(); + } + else if(matchSymbolSomft(Keyword::Model)) + { + result.myScope = ScopedValueKey::Scope::Model; + nextHard(); + } + else if(matchSymbolSomft(Keyword::This)) + { + result.myScope = ScopedValueKey::Scope::Element; + nextHard(); + } + else if(matchSymbolSomft(Keyword::SchoolOperator)) + { + result.myScope = ScopedValueKey::Scope::RemorialClass; + nextHard(); + } + else if(matchSymbolSomft(Keyword::Material)) + { + result.myScope = ScopedValueKey::Scope::Material; + nextHard(); + } + else if(peekSymbolSequence({Keyword::Colon, Keyword::Colon})) + { + result.myScope = ScopedValueKey::Scope::Global; + + if(!peekSymbolSequence({Keyword::Colon, Keyword::Colon, Keyword::UserToken})) + { + next(); + nextHard(); + } + } + + while(peekSymbolSequence({Keyword::Colon, Keyword::Colon, Keyword::UserToken})) + { + next(); + next(); + result.addToPath(getCurrentToken().userToken); + next(); + } + + return result; + }; + + auto parseScopedValueKeyForExpression = [&] + { + ScopedValueKey result = parseValueKeyScopeAndPath(); + + if(matchSymbolSomft(Keyword::Material)) + { + nextHard(); + matchSymbolHard(Keyword::Dot); + nextHard(); + matchSymbolHard(Keyword::UserToken); + + result.setKey(getCurrentToken().userToken); + + nextHard(); + + matchSymbolHard(Keyword::OpenParen); + + result.myScope = ScopedValueKey::Scope::Material; + result.parameter = std::make_unique(parseExpression()); + + matchSymbolHard(Keyword::CloseParen); + next(); + return result; + } + + if(!result.empty()) + { + matchSymbolHard(Keyword::Dot); + nextHard(); + } + + if(matchSymbolSomft(Keyword::UserToken)) + { + result.setKey(getCurrentToken().userToken); + } + else + { + const int sym = getCurrentToken().symbol; + + if(!attrKeyToBodyTypeMap.at(sym).expression) + throw SyntaxError(getCurrentToken(), "Invalid value for key, expected user token or attribute key."); + + result.setKey(getLiteralForSymbol(sym)); + } + + next(); + + result.setScopeIfUnset(ScopedValueKey::Scope::Element); + return result; + }; + + auto parseScopedValueKeyForStateKey = [&] + { + ScopedValueKey result = parseValueKeyScopeAndPath(); + + if(!result.empty()) + { + matchSymbolHard(Keyword::Dot); + nextHard(); + } + + matchSymbolHard(Keyword::UserToken); + result.setKey(getCurrentToken().userToken); + + next(); + + result.setScopeIfUnset(ScopedValueKey::Scope::Element); + return result; + }; + + auto parseScopedValueKeyForPositionWithin = [&] + { + ScopedValueKey result = parseValueKeyScopeAndPath(); + + if(result.myScope && + result.myScope != ScopedValueKey::Scope::Global && + result.myScope != ScopedValueKey::Scope::RemorialClass) + { + throw SyntaxError(result.definedAt, "Invalid scope for position-within, must be global or remorial."); + } + + if(!result.empty() && matchSymbolSomft(Keyword::Dot)) + { + nextHard(); + if(!matchSymbolSomft(Keyword::UserToken)) + throw SyntaxError(getCurrentToken().codePosition, + "Position-within requires user token as element or cell key."); + + result.setKey(getCurrentToken().userToken); + next(); + } + else if(result.empty()) + { + matchSymbolHard(Keyword::UserToken); + result.addToPath(getCurrentToken().userToken); + next(); + } + + if(result.empty()) + throw SyntaxError(result.definedAt, "Was expecting value key path."); + + result.setScopeIfUnset(ScopedValueKey::Scope::Global); + return result; + }; + + auto parseScopedValueKeyForModelPath = [&] + { + ScopedValueKey result = parseValueKeyScopeAndPath(); + + if(result.empty()) + { + matchSymbolHard(Keyword::UserToken); + result.addToPath(getCurrentToken().userToken); + next(); + } + + result.setScopeIfUnset(ScopedValueKey::Scope::Global); + + switch(*result.myScope) + { + case ScopedValueKey::Scope::Material: + case ScopedValueKey::Scope::Element: + throw SyntaxError(result.definedAt, "Invalid scope for model path. Must be remorial, generic model or global."); + default: break; + } + + return result; + }; + + std::function parseExpressionForNode = [&](const int depth) + { + auto parseValueNode = [&] + { + if(matchSymbolSomft(Keyword::OpenParen)) + { + nextHard(); + return parseExpressionForNode(depth + 1); + } + + ExpressionNode unitNode; + + if(matchSymbolSomft(Keyword::OpenSquare)) + { + unitNode.weakTerm = true; + nextHard(); + } + + if(matchSymbolSomft(Keyword::ScalarType) || matchSymbolSomft(Keyword::FloatType)) + { + if(unitNode.weakTerm) + { + throw SyntaxError(getCurrentToken(), "Only non-constant terms may be [weak]."); + } + + unitNode.val = getCurrentToken().userNumber; + unitNode.operation = Keyword::ScalarType; //Always because yes. + + next(); + + if(matchSymbolSomft(Keyword::UnitStandard) || + matchSymbolSomft(Keyword::UnitPercent)) + { + unitNode.operation = getCurrentToken().symbol; + next(); + } + else if(matchSymbolSomft(Keyword::UnitSeconds) || + matchSymbolSomft(Keyword::UnitMilliseconds)) + { + throw SyntaxError(getCurrentToken(), "Time units are not valid in scalar expression."); + } + } + else + { + try + { + unitNode.operation = Keyword::ScopedValueKey; + unitNode.extVal = parseScopedValueKeyForExpression(); + } + catch(SyntaxError&) + { + throw SyntaxError(getCurrentToken(), "Malformed Expression, expected Scalar or Value Key."); + } + } + + if(unitNode.weakTerm) + { + matchSymbolHard(Keyword::CloseSquare); + next(); + } + + return unitNode; + }; + + std::vector nodes; + + nodes.push_back(parseValueNode()); + + while(matchSymbolSomft(Keyword::OperatorPlus) || + matchSymbolSomft(Keyword::OperatorMinus) || + matchSymbolSomft(Keyword::OperatorTimes) || + matchSymbolSomft(Keyword::OperatorDivide)) + { + ExpressionNode operationNode; + operationNode.operation = getCurrentToken().symbol; + nodes.push_back(operationNode); + + nextHard(); + + nodes.push_back(parseValueNode()); + } + + if(matchSymbolSomft(Keyword::CloseParen) && depth != 0) next(); //c o n s u m e + + auto collapse = [&](const std::vector operators) + { + if(!nodes.size()) return; + + auto it = nodes.begin(); + while(true) + { + //Entry assumes x (op x) + // ^ + + auto leftIt = it; + + ++it; + + if(it == nodes.end()) break; //Singlet~ + + //Assuming x op x + // ^ + + auto middleIt = it; + + ++it; + //Assuming x op x (op x) + // ^ //Acceptable re-entry point. + + auto rightIt = it; + + //Note to anyone thinking of hiring me, I'm not necessarily proud of this one liner... + //Unless you are impressed. Then yeeeeee-haa boy am I proud! + if([&]{for(const int op : operators) if(middleIt->operation == op) return true; return false; }()) + { + middleIt->left = std::make_unique(*leftIt); + middleIt->right = std::make_unique(*rightIt); + + middleIt = nodes.erase(leftIt); + nodes.erase(middleIt + 1); + + it = middleIt; + } + } + }; + + collapse({Keyword::OperatorTimes, Keyword::OperatorDivide}); + collapse({Keyword::OperatorPlus, Keyword::OperatorMinus}); + + if(nodes.size() != 1) throw std::logic_error("THIS CAN'T FUCKING HAPPEN"); + + return nodes.front(); + }; + + parseExpression = [&] + { return Expression(parseExpressionForNode(0), getCurrentToken().codePosition); }; + + auto parseGraph = [&](ScopedVariables* scope = nullptr) + { + //graph ( linear|smooth , FLOAT @ 0-100 % ) + //nameOfGraph + + //oops TODO + //normalize-graph( nameOfGraph, ... ) + + std::optional optionalResult; + + if(matchSymbolSomft(Keyword::UserToken)) + { + const ValueKey key = getCurrentToken().userToken; + + auto justBeatIt = [&](auto& container) + { + auto it = container.find(key); + if(it != container.end()) + { + optionalResult = it->second; + return true; + } + return false; + }; + + if(!(scope && justBeatIt(scope->graphs))) + justBeatIt(varsForTranslationUnit.graphs); + + if(optionalResult) next(); + + return optionalResult; + } + + if(!matchSymbolSomft(Keyword::Graph)) return optionalResult; + + matchNextAdvance(Keyword::OpenParen); + nextHard(); + + int mode; + + if(matchSymbolSomft(Keyword::Linear)) mode = Keyword::Linear; + else if(matchSymbolSomft(Keyword::Smooth)) mode = Keyword::Smooth; + else throw SyntaxError(getCurrentToken(), "Graphs require interpolation mode to be explicitly defined."); + + nextHard(); + + Animation::Graph result(mode); + + bool gotSample = false; + + while(matchSymbolSomft(Keyword::Comma)) + { + nextHard(); + + auto matchNumber = [&] + { + //Must be a Keyword::Float of less than 1, or a scalar of 1 or 0 + // OR a scalar between 0 and 100, followed by a percent sign + if(!matchSymbolSomft(Keyword::ScalarType) && !matchSymbolSomft(Keyword::FloatType)) + throw SyntaxError(getCurrentToken(), Keyword::ScalarType); + + const double val = getCurrentToken().userNumber; + next(); + + if(matchSymbolSomft(Keyword::UnitPercent)) + { + next(); + + if(val < 0 || val > 100) + throw SyntaxError(getCurrentToken(), "Graph samples point percentage out of range."); + + return val / 100; + } + + if(val < 0 || val > 1) + throw SyntaxError(getCurrentToken(), "Floating point sample for graph out of range."); + + return val; + }; + + const std::string badNumber = "Invalid number range/type"; + + const double x = matchNumber(); + matchSymbolHard(Keyword::SchoolOperator); + nextHard(); + const double y = matchNumber(); + + result.addSample(y, x); + gotSample = true; + } + + if(!gotSample) throw SyntaxError(getCurrentToken(), "Graphs may not be empty (Why not? idek)."); + + matchSymbolHard(Keyword::CloseParen); + next(); + + if(!gotSample) return optionalResult; + optionalResult = result; + return optionalResult; + }; + + auto parseSet = [&](SetContainer* sets) + { + //Keyword::Set ScopedValueKey Keyword::EqualSign Expression Keyword::Semicolon + + if(!matchSymbolSomft(Keyword::Set)) return false; + + next(); + + matchSymbolHard(Keyword::Colon); + next(); + + ScopedValueKey target = parseScopedValueKeyForExpression(); + + matchSymbolHard(Keyword::EqualSign); + next(); + + Expression expr = parseExpression(); + + matchSymbolHard(Keyword::Semicolon); + next(); + + //Done actually parsing + + auto it = sets->find(target); + + if(it != sets->end()) + { + nameConflict("Set target", target.definedAt, it->first.definedAt); + return true; + } + + //Woof, that was a lot of work... + sets->operator[](target) = expr; + return true; + }; + + auto parseDeclare = [&](ScopedVariables* vars, ElementPrecursor* precursor = nullptr) + { + if(!matchSymbolSomft(Keyword::Declare)) return false; + + nextHard(); + + const int symbol = getCurrentToken().symbol; + const CodePosition typePos = getCurrentToken().codePosition; + + nextHard(); + matchSymbolHard(Keyword::Colon); + nextHard(); + matchSymbolHard(Keyword::UserToken); + + const std::string declaredName = getCurrentToken().userToken; + + nextHard(); + matchSymbolHard(Keyword::EqualSign); + nextHard(); + + auto insertIntoExclusiveMap = [&](auto* container, auto val) + { + auto it = container->find(declaredName); + + if(it != container->end()) + { + nameConflict("Variable name", val.definedAt, it->second.definedAt); + return; + } + + container->operator[](declaredName) = val; + }; + + switch(symbol) + { + case Keyword::Graph: + { + auto optionalGraph = parseGraph(); + + if(!optionalGraph) throw SyntaxError(getCurrentToken().codePosition, + "Was expecting graph literal."); + + insertIntoExclusiveMap(precursor ? &precursor->vars.graphs + : &vars->graphs, + *optionalGraph); + break; + } + case Keyword::Expression: + { + insertIntoExclusiveMap(precursor ? &precursor->vars.defines + : &vars->defines, + parseExpression()); + break; + } + case Keyword::ScalarKeyword: + { + if(!precursor) throw SyntaxError(getCurrentToken().codePosition, + "Scalar declarations can only appear within the default " + "state of elements."); + + //TODO: Name collision checks? It seems idiomatic that the last one would just overwrite + // the previous. Maybe post a warning for the linter + precursor->elem.setInitialExpression(declaredName, parseExpression()); + break; + } + default: throw SyntaxError(typePos, "This type is invalid for this case, you absolute madperson."); + } + + matchSymbolHard(Keyword::Semicolon); + next(); + + return true; + }; + + auto parseAttributeWithExpressionType = [&](const int key, ReferenceAttribute* attr) + { + if(!attrKeyToBodyTypeMap.at(key).expression) return false; + + typedef bool lol; + lol gotem = false; + + auto processNoTarget = [&]() + { + gotem = true; + attr->active = true; + return parseExpression(); + }; + + auto processConstraintTarget = [&]() + { + matchNextAdvance(Keyword::EqualSign); + nextHard(); + auto optional = processNoTarget(); + return optional; + }; + + if(matchSymbolSomft(Keyword::Start)) attr->starting = processConstraintTarget(); + else if(matchSymbolSomft(Keyword::Min)) attr->min = processConstraintTarget(); + else if(matchSymbolSomft(Keyword::Max)) attr->max = processConstraintTarget(); + else attr->expr = processNoTarget(); + + return gotem; + }; + + auto parseTimeInMilliseconds = [&] + { + if(!matchSymbolSomft(Keyword::ScalarType)) return std::optional(); + + int result = getCurrentToken().userNumber; + nextHard(); + + if(matchSymbolSomft(Keyword::UnitMilliseconds)); + else if(matchSymbolSomft(Keyword::UnitSeconds)) + result = result * 1000; + else throw SyntaxError(getCurrentToken(), "Was expecting chrono type"); + + next(); + + return std::optional(result); + }; + + auto parseAttributeDelay = [&](ReferenceAttribute* attr) + { + if(!matchSymbolSomft(Keyword::Delay)) return false; + nextHard(); + matchSymbolHard(Keyword::OpenParen); + nextHard(); + + attr->delay = parseTimeInMilliseconds(); + + //Actually, I'm in a forgiving mood. I don't really care if it's empty. + if(attr->delay) attr->active = true; + + matchSymbolHard(Keyword::CloseParen); + next(); + return true; + }; + + struct EasePair + { + std::optional easeIn; + std::optional easeOut; + }; + + auto parseAttributeEase = [&](const int key, ScopedVariables* scope) + { + std::optional optionalResult; + + if(!attrKeyToBodyTypeMap.at(key).expression) return optionalResult; + if(!matchSymbolSomft(Keyword::EaseIn) && !matchSymbolSomft(Keyword::EaseOut)) return optionalResult; + + const int easeType = getCurrentToken().symbol; + nextHard(); + matchSymbolHard(Keyword::OpenParen); + nextHard(); + + auto optionalTime = parseTimeInMilliseconds(); + if(!optionalTime) throw SyntaxError(getCurrentToken(), "Ease statements require time as the first arguement."); + + matchSymbolHard(Keyword::Comma); + nextHard(); + auto optionalGraph = parseGraph(scope); + + if(!optionalGraph) throw SyntaxError(getCurrentToken(), "Ease statements require a graph. " + "Use \"delay\" if you just want to delay."); + + + matchSymbolHard(Keyword::CloseParen); + next(); + + Animation::Transition myTransition = {*optionalTime, *optionalGraph}; + + EasePair result; + + if(easeType == Keyword::EaseIn) result.easeIn = myTransition; + else if(easeType == Keyword::EaseOut) result.easeOut = myTransition; + else throw std::logic_error("hahahahahahahaahahhh, whew *whipes tear* heh"); + + return std::optional(result); + }; + + auto parseAttributeBodyForKey = [&](const int key, ReferenceAttribute* attr) + { + const CodePosition startPos = getCurrentToken().codePosition; + + const AttributeBodyTypes types = attrKeyToBodyTypeMap.at(key); + + auto parseStateKeyListType = [&](const int key, ReferenceAttribute* attr) + { + if(!types.stateKeyList) return false; + + attr->active = true; + attr->stateModifierAttr = true; + + attr->keys.push_back(parseScopedValueKeyForStateKey()); + return true; + }; + + auto parseAttributeWithSingleScopedValueKeyType = [&](const int key, ReferenceAttribute* attr) + { + if(!types.singleScopedValueKey) return false; + + attr->active = true; + attr->singleKey = parseScopedValueKeyForExpression(); + return true; + }; + + auto parseAttributeWithUserTokenListType = [&](const int key, ReferenceAttribute* attr) + { + if(!types.userTokenList) return false; + if(!matchSymbolSomft(Keyword::UserToken)) return false; + + attr->active = true; + attr->literalList.push_back(getCurrentToken().userToken); + next(); + + return true; + }; + + auto parseAttributeWithUserTokenType = [&](const int key, ReferenceAttribute* attr) + { + if(!types.userToken) return false; + if(!matchSymbolSomft(Keyword::UserToken)) return false; + + attr->active = true; + attr->literal = getCurrentToken().userToken; + next(); + + return true; + }; + + auto parsePositionWithinType = [&](const int key, ReferenceAttribute* attr) + { + if(!types.positionWithin) return false; + + attr->active = true; + attr->singleKey = parseScopedValueKeyForPositionWithin(); + + //position within is the only case where a single key isn't a VALUE, but instead a + // path. This complicates things considerably. + + return true; + }; + + + auto parseAttributeWithColorType = [&](const int key, ReferenceAttribute* attr) + { + if(!types.color) return false; + if(!matchSymbolSomft(Keyword::ColorLiteral)) + { + if(!matchSymbolSomft(Keyword::UserToken)) return false; + //Colors can now be tokens, and an arbitrary number of them to boot... + + std::string builtColorKey; + + while(matchSymbolSomft(Keyword::UserToken)) + { + if(builtColorKey.size()) builtColorKey.push_back(' '); + builtColorKey += getCurrentToken().userToken; + + next(); + } + + attr->active = true; + attr->color = Color(builtColorKey); //May or may not be correct. + return true; + + } + + attr->active = true; + attr->color = getCurrentToken().userColor; + next(); + + return true; + }; + + auto parseAttributeWithStringLiteralType = [&](const int key, ReferenceAttribute* attr) + { + if(!types.stringLiteral) return false; + if(!matchSymbolSomft(Keyword::UserString)) return false; + + attr->active = true; + attr->literal = getCurrentToken().userString; + next(); + + return true; + }; + + auto parseAttributeWithPropertyType = [&](const int key, ReferenceAttribute* attr) + { + if(!types.property) return false; + + const int sym = getCurrentToken().symbol; + //XXXXXXXXXXXX sym not key!!!! XXX + // I think we can remove key from all of these then + if(!(sym >= Property::PropertiesStart && key <= Property::PropertiesEnd)) return false; + + attr->active = true; + attr->property = sym; + next(); + + return true; + }; + + //parseAttributeWithExpression is the only one that fails hard, but it's also exclusive, + // an attribute can't***** be an expression type in addition to any other. + //*****currently... + + + //Need to match the appropriate parsers with the key and then have a specific + // message if it fails~ + + if(parseAttributeWithPropertyType(key, attr)); + else if(parseAttributeWithColorType(key, attr)); + else if(parseAttributeWithUserTokenType(key, attr)); + else if(parseAttributeWithStringLiteralType(key, attr)); + else if(parseAttributeWithUserTokenListType(key, attr)); + else if(parsePositionWithinType(key, attr)); + else if(parseStateKeyListType(key, attr)); + else if(parseAttributeWithSingleScopedValueKeyType(key, attr)); + else if(parseAttributeWithExpressionType(key, attr)); + else + { + std::stringstream ss; + + ss << "Malformed attribute body, expected "; + + bool doneDidIt = false; + auto additionally = [&](const std::string str) + { + if(doneDidIt) ss << ", or "; + ss << str; + doneDidIt = true; + }; + + if(types.stateKeyList) additionally("State Key List"); + if(types.userTokenList) additionally("User Token List"); + if(types.userToken) additionally("User Token"); + if(types.positionWithin) additionally("Position Within Key"); + if(types.color) additionally("Color Literal"); + if(types.stringLiteral) additionally("String Literal"); + if(types.property) additionally("Property"); + if(types.expression) additionally("Expression"); + + ss << "."; + + throw SyntaxError(startPos, ss.str()); + } + }; + + auto parseUnnaturalAttributeBody = [&](const int key, ReferenceAttributeSet* attrs, ScopedVariables* scope) + { + //We just assume we're not merging in, here. + //key: one; + //key: one, one, one, one; + + const CodePosition startPos = getCurrentToken().codePosition; + const int baseKey = unnaturalKeyMap.at(key)[0]; //xxx + + std::vector> myAttributes; + + while(true) + { + if(matchSymbolSomft(Keyword::Pass)) + { + myAttributes.emplace_back(); + next(); + } + else if(matchSymbolSomft(Keyword::Clear)) + { + ReferenceAttribute temp; + temp.active = true; + temp.clear = true; + myAttributes.emplace_back(temp); + next(); + } + else if(parseAttributeEase(key, scope)) + throw SyntaxError(startPos, "Unnatural key cannot contain ease specifiers."); + else if(ReferenceAttribute dummy; parseAttributeDelay(&dummy)) + throw SyntaxError(startPos, "Unnatural key cannot contain delay specifiers."); + else + { + ReferenceAttribute temp; + temp.active = true; + parseAttributeBodyForKey(key, &temp); + myAttributes.emplace_back(temp); + } + + if(matchSymbolSomft(Keyword::Comma)) + { + nextHard(); + continue; + } + break; + } + + + if(myAttributes.size() == 1) + { + for(const int natKey : unnaturalKeyMap.at(key)) + if(myAttributes.front()) + attrs->insertAttribute(*myAttributes.front(), natKey); + } + else if(myAttributes.size() == unnaturalKeyMap.at(key).size()) + { + auto it = myAttributes.begin(); + for(const int natKey : unnaturalKeyMap.at(key)) + { + if(it->has_value()) attrs->insertAttribute(**it, natKey); + ++it; + } + } + else + { + std::stringstream ss; + ss << "Invalid number of arguments to unatural key " + << getLiteralForSymbol(key) << ", must be 1 or " + << unnaturalKeyMap.at(key).size() << "."; + throw SyntaxError(startPos, ss.str()); + } + + matchSymbolHard(Keyword::Semicolon); + next(); + }; + + auto parseAttribute = [&](ReferenceAttributeSet* attrs, ScopedVariables* scope) + { + const int key = getCurrentToken().symbol; + + if(!attrKeyToBodyTypeMap.count(key)) + return false; + + nextHard(); + matchSymbolHard(Keyword::Colon); + nextHard(); + + if(matchSymbolSomft(Keyword::Semicolon)) + { + next(); + return true; + } + + if(attrKeyToBodyTypeMap.at(key).unnatural) + { + parseUnnaturalAttributeBody(key, attrs, scope); + return true; + } + + ReferenceAttribute myAttr = attrs->getAttribute(key); + + while(true) + { + if(matchSymbolSomft(Keyword::Clear)) + { + myAttr.active = true; + myAttr.clear = true; + nextHard(); + } + else if(matchSymbolSomft(Keyword::Pass)) nextHard(); //Inline parsing lmfao + else if(auto pair = parseAttributeEase(key, scope)) + { + myAttr.active = true; //Really? + if(pair->easeIn) myAttr.ease = pair->easeIn; + assert(!pair->easeOut); //Oof just a quickie I'm busy with something else right now. + } + else if(parseAttributeDelay(&myAttr)); + else parseAttributeBodyForKey(key, &myAttr); + + if(matchSymbolSomft(Keyword::Comma)) + { + nextHard(); + continue; + } + + break; + } + + matchSymbolHard(Keyword::Semicolon); + next(); + + attrs->insertAttribute(myAttr, key); + + return true; + }; + + typedef VirtualStateKeyPrecursor::Node VstateKeyNode; + + std::function parseVirtualStateKey = [&](const int parenLevel) + { + //Left node is always either just a ScopedValueKey or a (Node). + //Right node is always parsed as a full VirtualStateKey + // so that (1 & 1 & 1) == (1 & (1 & (1))) + + auto parseValueNode = [&]() + { + bool negation = false; + if(matchSymbolSomft(Keyword::Not)) + { + nextHard(); + negation = true; + } + + if(matchSymbolSomft(Keyword::OpenParen)) + { + nextHard(); + + VstateKeyNode node = parseVirtualStateKey(parenLevel + 1); + + if(negation) node.negation = !node.negation; + return node; + } + + VstateKeyNode node; + node.operation = Keyword::ScopedValueKey; + node.negation = negation; + node.stateKeyPrecursor = parseScopedValueKeyForStateKey(); + return node; + }; + + VstateKeyNode resultNode = parseValueNode(); + + while(!matchSymbolSomft(Keyword::Colon) && + !matchSymbolSomft(Keyword::CloseParen)) + { + VstateKeyNode operatorNode; + operatorNode.left = std::make_unique(resultNode); + + if(matchSymbolSomft(Keyword::And)) operatorNode.operation = Keyword::And; + else if(matchSymbolSomft(Keyword::Or)) operatorNode.operation = Keyword::Or; + else if(matchSymbolSomft(Keyword::Xor)) operatorNode.operation = Keyword::Xor; + else throw SyntaxError(getCurrentToken(), "Was expecting logical operator for compound state key."); + + next(); + + operatorNode.right = std::make_unique(parseValueNode()); + resultNode = operatorNode; + } + + if(parenLevel) matchSymbolHard(Keyword::CloseParen); + else matchSymbolHard(Keyword::Colon); + next(); + + return resultNode; + }; + + auto parseElementBody = [&](ElementPrecursor* precursor) + { + if(matchSymbolSomft(Keyword::Semicolon)) + { + next(); + return; + } + + if(!matchSymbolSomft(Keyword::OpenBracket)) + { + const CodePosition codeMarker = getCurrentToken().codePosition; + postSyntaxError(SyntaxError(codeMarker, "Malformed element body. Was expecting { or ;\n" + "Attempting to make sense of this...")); + + advanceTo({Keyword::OpenBracket, Keyword::CloseBracket}); + if(Keyword::CloseBracket) + { + next(); + return; + } + } + + matchSymbolHard(Keyword::OpenBracket); + nextHard(); + + ScopedValueKey currentStateKey{}; + + while(true) + { + try + { + const bool defaultState = !currentStateKey.key.has_value(); + ReferenceAttributeSet* currentState = &precursor->elem.getAttributeSetForStateKey(currentStateKey); + + if(defaultState) + { + while(parseDeclare(&precursor->vars, precursor) || + parseAttribute(currentState, &precursor->vars)); + } + else + { + while(parseSet(¤tState->setModifiers) || + parseAttribute(currentState, &precursor->vars)); + } + + if(matchSymbolSomft(Keyword::State)) + { + nextHard(); + + VirtualStateKeyPrecursor vstatekey; + + //Alright furries, hold onto your peets. + vstatekey.root = parseVirtualStateKey(0); + + if(vstatekey.checkIsVirtual()) currentStateKey = vstatekey.root.stateKeyPrecursor; + else + { + currentStateKey = ScopedValueKey{}; + std::stringstream ss; + ss << "generated-virtual-state-" << precursor->getFreshVirtualStateStamp(); + currentStateKey.myScope = ScopedValueKey::Scope::Element; + currentStateKey.key = ss.str(); + vstatekey.proxyStateKeyPrecursor = currentStateKey; + + precursor->elem.addVirtualStateKey(vstatekey); + } + + continue; + } + + if(!matchSymbolSomft(Keyword::CloseBracket)) + { + if(matchSymbolSomft(Keyword::Set) && defaultState) + { + postSyntaxError(SyntaxError(getCurrentToken(), "Set not allowed in default state.")); + SetContainer dump; + parseSet(&dump); //But aye, might as well compile it, eh? + continue; //Hey! We can recover ourselves here! + } + else if(matchSymbolSomft(Keyword::Declare) && !defaultState) + throw SyntaxError(getCurrentToken(), "Declare not allowed in non-default state."); + else throw SyntaxError(getCurrentToken(), "Token is not a state or variable modifier or attribute key."); + } + + break; + } + catch(const SyntaxError& e) + { + postSyntaxError(e); + //We can look into the symbol and offer hints! HOW CUTE IS THAAAAAT + //"hint: did you mean [x]?" *squee* + if(!matchSymbolSomft(Keyword::Semicolon)) + advanceTo({Keyword::Semicolon, Keyword::CloseBracket, Keyword::State}); + if(matchSymbolSomft(Keyword::Semicolon)) next(); + continue; + } + } + + matchSymbolHard(Keyword::CloseBracket); + next(); + }; + + auto tryWithinHeader = [&](auto fun) + { + try + { + fun(); + } + catch(SyntaxError& e) + { + //TODO would like to add "Advancing to X to the error". + //Maybe pass the error object to the advanceTo function? + + postSyntaxError(e); + advanceTo({Keyword::Semicolon, Keyword::OpenBracket, Keyword::CloseBracket}); + } + }; + + auto parseElementHeaderClass = [&](ElementPrecursor* precursor) + { + tryWithinHeader([&] + { + //: usertoken (, usertoken (...)) + if(!matchSymbolSomft(Keyword::Colon)) return; + nextHard(); + + while(true) + { + matchSymbolHard(Keyword::UserToken); + precursor->myClasses.push_back({getCurrentToken().codePosition, getCurrentToken().userToken}); + + next(); + if(matchSymbolSomft(Keyword::Comma)) + { + next(); + continue; + } + break; + } + }); + }; + + auto parseElementHeaderModel = [&](ElementPrecursor* precursor) + { + //usertoken (:: usertoken (:: usertoken (...))) + tryWithinHeader([&] + { + if(!matchSymbolSomft(Keyword::Arrow)) return; + nextHard(); + + precursor->myModel = parseScopedValueKeyForModelPath(); + }); + }; + + auto parseElement = [&] + { + //# (|elementName) (HEADER|BODY) + if(!matchSymbolSomft(Keyword::Pound)) return false; + + ElementPrecursor precursor(allocateElementPrecursor()); + precursor.myType = ElementType::IsInstance; + nextHard(); //Commited now boiii + + if(matchSymbolSomft(Keyword::UserToken)) + { + precursor.name = getCurrentToken().userToken; + nextHard(); + } + else + { + precursor.autoGenerateRandomName(); + } + + parseElementHeaderModel(&precursor); + parseElementHeaderClass(&precursor); + parseElementBody(&precursor); + precursors.push_back(precursor); + + return true; + }; + + auto parseClass = [&] + { + if(!matchSymbolSomft(Keyword::Dot)) return false; + + ElementPrecursor precursor(allocateElementPrecursor()); + precursor.myType = ElementType::IsClass; + + nextHard(); + matchSymbolHard(Keyword::UserToken); + + precursor.name = getCurrentToken().userToken; + + next(); //name + + parseElementHeaderClass(&precursor); + parseElementBody(&precursor); + precursors.push_back(precursor); + + return true; + }; + + auto parseRemora = [&] + { + //@ remoraHost . USERTOKEN (HEADER|BODY) + //@remoraHost (header|body) //Anonymous + if(!matchSymbolSomft(Keyword::SchoolOperator)) return false; + nextHard(); + matchSymbolHard(Keyword::UserToken); + + ElementPrecursor precursor(allocateElementPrecursor()); + precursor.myType = ElementType::IsRemora; + precursor.hostName = getCurrentToken(); + + next(); + + if(peekSymbolSequence({Keyword::Dot, Keyword::UserToken})) + { + next(); + precursor.name = getCurrentToken().userToken; + next(); + } + else precursor.autoGenerateRandomName(); + + parseElementHeaderModel(&precursor); + parseElementHeaderClass(&precursor); + parseElementBody(&precursor); + precursors.push_back(precursor); + + return true; + }; + + try + { + if(!matchSymbolSomft(Spec::spec)) + throw SyntaxError(getCurrentToken(), "Expected Spec declaration."); + + nextHard(); + + //TODO: Put the version specification somewhere else + if(!matchSymbolSomft(Spec::bleeding)) + throw SyntaxError(getCurrentToken(), + "IVD version mismatch. Current version is ""bleeding"" Aborting..."); + + nextHard(); + matchSymbolHard(Keyword::Semicolon); + nextHard(); + + while(!reachedEnd()) + { + if(parseElement()); + else if(parseClass()); + else if(parseRemora()); + else if(parseDeclare(&varsForTranslationUnit)); + else + { + postSyntaxError(SyntaxError(getCurrentToken(), "Invalid token for base context.")); + break; + } + } + } + catch(SyntaxError& e) + { + postSyntaxError(e); + } + catch(UnexpectedEOF& e) + { + errorMessages.push_back("Unexpected EOF, aborting..."); + } + catch(std::logic_error& e) + { + std::string es = "Logic error: "; + es += e.what(); + errorMessages.push_back(es); + } + + return precursors; +} + +void Compiler::finalizePrecursors(std::vector precursors) +{ + std::map virginClasses; + + for(ElementPrecursor& preElem : precursors) + { + { + //This is kind of dumb... TODO? + //later: Uh, I tried... But uh... Is it really that dumb? + preElem.elem.getDefaultAttr().declareModifiers = preElem.vars.defines; + } + + //Backwards because Element::deriveFrom is kind of backwards. + for(auto rit = preElem.myClasses.rbegin(); rit != preElem.myClasses.rend(); ++rit) + { + + const UserToken parentClassName = *rit; + { + //First request for the class finalizes it. + auto it = virginClasses.find(parentClassName.value); + virginClasses.count(parentClassName.value); + if(it != virginClasses.end()) + { + //Already deduplicated before inserting into virginClasses. + myClasses.emplace(parentClassName.value, it->second); + virginClasses.erase(it); + } + } + + auto it = myClasses.find(parentClassName.value); + if(it == myClasses.end()) + { + postSyntaxError(SyntaxError(parentClassName.pos, "Parent class undefined.")); + continue; + } + + preElem.elem.deriveFrom(it->second.elem); + + { + std::map names; + + for(auto remora : preElem.remoras) + { + auto it = names.find(remora.name); + + if(it == names.end()) names[remora.name] = remora.codePosition; + else nameConflict("Remora name", remora.codePosition, it->second); + } + } + + RustUtils::Routine::appendContainer(preElem.remoras, it->second.remoras); + } + + if(preElem.myType == ElementType::IsClass) + { + //Name collision cheeeeeeck... + auto virginIt = virginClasses.find(preElem.name); + + if(virginIt != virginClasses.end()) + { + nameConflict("Class name", + preElem.codePosition, + virginIt->second.codePosition); + + continue; + } + + auto finalIt = myClasses.find(preElem.name); + + if(finalIt != myClasses.end()) + { + nameConflict("Class name", + preElem.codePosition, + finalIt->second.codePosition); + + continue; + } + + virginClasses.emplace(preElem.name, preElem); + } + else if(preElem.myType == ElementType::IsRemora) + { + auto it = virginClasses.find(preElem.hostName.value); + if(it == virginClasses.end()) + { + if(myClasses.find(preElem.hostName.value) != myClasses.end()) + postSyntaxError(SyntaxError(preElem.hostName.pos, + "Remora host class already finalized.")); + else postSyntaxError(SyntaxError(preElem.hostName.pos, + "Remora host class undefined.")); + continue; + } + + it->second.remoras.emplace_back(preElem); + } + else if(preElem.myType == ElementType::IsInstance) + { + if(!preElem.myModel.empty() && preElem.myModel.myScope != ScopedValueKey::Scope::Global) + { + postSyntaxError(SyntaxError(preElem.myModel.definedAt, + "Cannot enumerate free element across non-global model.")); + continue; + } + + { + auto nameIt = elementNamePositions.find(preElem.name); + + if(nameIt != elementNamePositions.end()) + { + nameConflict("Element name", + preElem.codePosition, + nameIt->second); + continue; + } + } + + if(preElem.myModel.path) preElem.elem.setModelPath(*preElem.myModel.path); + preElem.elem.setPath({preElem.name}); + + auto substituteValueKeys = [&](ScopedValueKey& vkey, const ElementPrecursor& parent) + { + if(vkey.myScope == ScopedValueKey::Scope::RemorialClass) + { + if(!vkey.path) vkey.path = ValueKeyPath(); + + ValueKeyPath mewPath; + mewPath = parent.elem.getPath(); + RustUtils::Routine::appendContainer(mewPath, *vkey.path); + vkey.path = mewPath; + vkey.myScope = ScopedValueKey::Scope::Global; + } + }; + + auto substituteParentKeys = std::bind(substituteValueKeys, std::placeholders::_1, preElem); + + preElem.elem.applyToEachScopedValueKey(substituteParentKeys); + + std::function finalizeRemoras = [&](ElementPrecursor& parentElem) + { + for(ElementPrecursor& remora : parentElem.remoras) + { + { + ValueKeyPath mewPath = parentElem.elem.getPath(); + RustUtils::Routine::appendContainer(mewPath, {remora.name}); + remora.elem.setPath(mewPath); + } + + remora.elem.applyToEachScopedValueKey(std::bind(substituteValueKeys, std::placeholders::_1, parentElem)); + + //MODEL NAMES, PEOPLE + if(!remora.myModel.empty()) + { + if(remora.myModel.myScope == ScopedValueKey::Scope::RemorialClass) + { + if(parentElem.elem.getModelPath().empty()) + { + postSyntaxError(SyntaxError(parentElem.codePosition, "Remorial instance does not declare a model.")); + postSyntaxError(SyntaxError(remora.myModel.definedAt, "But remora requires one.")); + } + else + { + ValueKeyPath modelPath = parentElem.elem.getModelPath(); + if(remora.myModel.path) + RustUtils::Routine::appendContainer(modelPath, *remora.myModel.path); + remora.elem.setModelPath(modelPath); + } + } + else + { + throw SyntaxError(remora.myModel.definedAt, + "Attempt to enumerate remora across non-host model."); + } + } + + finalizeRemoras(remora); + } + }; + + try + { + finalizeRemoras(preElem); + + std::function recursivelyInsert = [&](ElementPrecursor& preElem) + { + elementNamePositions[preElem.name] = preElem.codePosition; + + preElem.elem.applyToEachScopedValueKey([&](ScopedValueKey& key) + { + if(key.myScope == ScopedValueKey::Scope::RemorialClass) + throw std::logic_error("shit."); + + if(!preElem.myModel.path && + key.myScope == ScopedValueKey::Scope::Model) + { + postSyntaxError(SyntaxError(key.definedAt, + "Model scoped value key in non-enumerated element.")); + } + }); + + finalizedElements.push_back(preElem.elem); + for(ElementPrecursor& remora : preElem.remoras) + recursivelyInsert(remora); + }; + recursivelyInsert(preElem); + } + catch(SyntaxError& e) + { postSyntaxError(e); } + } else throw std::logic_error("Internal error: Unrecognized type for element precursor."); + } + + //Any classes that aren't already finalized need that to happen now, + // or else they'll turn into a pumpkin. + for(auto& pair : virginClasses) myClasses.emplace(pair.first, pair.second); +} + +bool Compiler::compileFile(const char* path) +{ + std::ifstream myFile; + myFile.open(path); + + std::string code; + + if(!myFile) return false; + + while(true) + { + std::string buffer; + std::getline(myFile, buffer); + + if(!myFile) + break; + + code += buffer; + code += '\n'; //For tracking line numbers. + } + + compile(code.c_str()); + return true; +} + +void Compiler::compile(std::string code) +{ + { + //If you use tabs 🔪 + std::string cleanedCode; + for(auto it = code.begin(); it != code.end(); ++it) + { + if(*it == '\t') cleanedCode += " "; + else cleanedCode += *it; + } + code = cleanedCode; + } + + finalizePrecursors(parseTokens(tokenizeInput(code))); + + for(const SyntaxError& e : myErrors) errorMessages.push_back(e.printout(code.c_str())); + myErrors.clear(); +} + +const std::string& Compiler::getErrorMessageDigest() +{ + publicErrorBuffer.clear(); + + for(const auto err : getErrorMessages()) + { + publicErrorBuffer += err; + publicErrorBuffer += '\n'; + } + + return publicErrorBuffer; +} + +}//IVD diff --git a/src/compiler.h b/src/compiler.h new file mode 100755 index 0000000..995a074 --- /dev/null +++ b/src/compiler.h @@ -0,0 +1,265 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef COMPILER_H +#define COMPILER_H + +#include + +#include "element.h" + +#include "graph.h" +#include "codeposition.h" + +#include "attributebodytypes.h" + +#include + +namespace IVD +{ + +struct ScopedVariables +{ + DefineContainer defines; + std::map graphs; +}; + +struct Token +{ + CodePosition codePosition; + const int symbol; + + std::string userToken; + std::string userString; + double userNumber; + Color userColor; + + Token(const CodePosition codePosition, const int sym): + codePosition(codePosition), + symbol(sym) + {} +}; + +class UnexpectedEOF : public std::exception {}; +class SyntaxError : public std::exception +{ + const CodePosition pos; + + //I understand that std::string can throw, but this error object is + // only thrown on syntax errors, not exactly a likely time for + // an std::string to throw... The problem is that I want to construct + // error messages conditionally and c strings won't work. + //We could store the state to regenerate them but ehhh... + std::optional errStr; + + struct Expecting + { + int expected; + int got; + }; + std::optional expecting; + std::optional conflictsWith; + +public: + SyntaxError(const Token theTok, const int expected): + pos(theTok.codePosition), + expecting({expected, theTok.symbol}) + {} + + SyntaxError(const Token theTok, const std::string errStr): + pos(theTok.codePosition), + errStr(errStr) + {} + + SyntaxError(const CodePosition pos): pos(pos) {} + + SyntaxError(const CodePosition pos, const std::string errStr): + pos(pos), + errStr(errStr) + {} + + SyntaxError(const std::string errStr, + const CodePosition pos, + const CodePosition conflictsWith): + errStr(errStr), + pos(pos), + conflictsWith(conflictsWith) + {} + + std::string printout(const char* context) const; +}; + +class Compiler +{ + int lastElementStamp; + + std::vector errorMessages; + std::string publicErrorBuffer; + //Warnings? + + std::map elementNamePositions; + std::list finalizedElements; + + std::map tokenToSymbolMap; + std::map symbolToTokenMap; + + AttributeBodyTypeMap attrKeyToBodyTypeMap; + + const std::set delimitingSymbolSet; + + std::map> unnaturalKeyMap; + + std::map> validPropertySetMap; + + enum class ElementType + { + IsClass, + IsRemora, + IsInstance, + }; + + struct UserToken + { + CodePosition pos; + std::string value; + + UserToken& operator=(const Token& tok) + { + pos = tok.codePosition; + value = tok.userToken; + return *this; + } + }; + + struct ElementPrecursor + { + CodePosition codePosition; + int stamp; + int lastVirtualStateStamp; + + Element elem; + ElementType myType; + std::string name; + UserToken hostName; + ScopedValueKey currentState; //Doesn't need to be here. + + std::vector myClasses; + + ScopedValueKey myModel; + std::vector remoras; + + ScopedVariables vars; + + ElementPrecursor(const CodePosition codePosition, const int stamp, const int attrCount): + codePosition(codePosition), + stamp(stamp), + lastVirtualStateStamp(0), + elem(stamp, attrCount), + currentState(), + myModel() + {} + + int getFreshVirtualStateStamp() + { return lastVirtualStateStamp++; } + + void autoGenerateRandomName() + { + std::stringstream ss; + std::stringstream generatedName; + generatedName << "anonymous-" << stamp << "-HORRIBLY-MAANGLED"; + name = generatedName.str(); + } + }; + + bool checkSymbolIsNaturalAttributeKey(const int sym) + { + const AttributeBodyTypes types = attrKeyToBodyTypeMap.at(sym); + return !types.unnatural; + } + + int getKeyspaceSize() + { return tokenToSymbolMap.size(); } + + int getFreshElementStamp() + { return lastElementStamp++; } + + std::map myClasses; + //The idea is that the classes can persist across + // multiple source files, but elements cannot. + //And that elements can be accessed directly by + // the user, but classes cannot. + + std::vector myErrors; //I mean, they're the user's, but... + + void postSyntaxError(const SyntaxError& err) { myErrors.push_back(err); } + void nameConflict(const std::string err, + const CodePosition pos1, + const CodePosition pos2) + { postSyntaxError(SyntaxError(err.c_str(), pos1, pos2)); } + + std::vector tokenizeInput(const std::string code); + std::vector parseTokens(std::vector tokens); + void finalizePrecursors(std::vector precursors); + + + + bool checkIsDelimitingSymbol(const int sym) + { return delimitingSymbolSet.count(sym); } + + int getSymbolKeyspaceSize() + { return tokenToSymbolMap.size(); } + +public: + Compiler(): + lastElementStamp(0), + tokenToSymbolMap(getTokenToSymbolMap()), + symbolToTokenMap(getSymbolToTokenMap()), + attrKeyToBodyTypeMap(getStandardAttributes()), + delimitingSymbolSet(getDelimitingSymbolSet()), + unnaturalKeyMap(getNaturalKeysToUnnaturalKeyMap()), + validPropertySetMap(getAttributeKeyToValidPropertyList()) + {} + + bool compileFile(const char* path); + void compile(std::string code); + + std::optional getSymbolForLiteral(const std::string token) + { + std::optional result; + auto it = tokenToSymbolMap.find(token); + if(it != tokenToSymbolMap.end()) + result = it->second; + + return result; + } + + std::string getLiteralForSymbol(const int sym) + { + auto it = symbolToTokenMap.find(sym); + + if(it == symbolToTokenMap.end()) + return "UNDEFINED (todo lol)"; + + return it->second; + } + + //Would love for this to be const + // but I don't have all day TODO + std::list& getElements() + { return finalizedElements; } + + Element* getElementForClass(const std::string className) + { + if(myClasses.count(className)) + return &myClasses.at(className).elem; + return nullptr; + } + + std::vector getErrorMessages() + { return errorMessages; } + + const std::string& getErrorMessageDigest(); +}; + +}//IVD + +#endif // COMPILER_H diff --git a/src/corefonts.h b/src/corefonts.h new file mode 100644 index 0000000..8492dbc --- /dev/null +++ b/src/corefonts.h @@ -0,0 +1,42 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +//Sans +extern const unsigned char corefontsans[]; +extern const int corefontsansSize; + +extern const unsigned char corefontsansbold[]; +extern const int corefontsansboldSize; + +extern const unsigned char corefontsansitalic[]; +extern const int corefontsansitalicSize; + +extern const unsigned char corefontsansbolditalic[]; +extern const int corefontsansbolditalicSize; + +//Serif +extern const unsigned char corefontserif[]; +extern const int corefontserifSize; + +extern const unsigned char corefontserifbold[]; +extern const int corefontserifboldSize; + +extern const unsigned char corefontserifitalic[]; +extern const int corefontserifitalicSize; + +extern const unsigned char corefontserifbolditalic[]; +extern const int corefontserifbolditalicSize; + +//Mono +extern const unsigned char corefontmono[]; +extern const int corefontmonoSize; + +extern const unsigned char corefontmonobold[]; +extern const int corefontmonoboldSize; + +extern const unsigned char corefontmonoitalic[]; +extern const int corefontmonoitalicSize; + +extern const unsigned char corefontmonobolditalic[]; +extern const int corefontmonobolditalicSize; diff --git a/src/defaults.cpp b/src/defaults.cpp new file mode 100644 index 0000000..1d3e663 --- /dev/null +++ b/src/defaults.cpp @@ -0,0 +1,258 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "defaults.h" +#include "displayitem.h" +#include "environment.h" + +#include "corefonts.h" + +#include +#include + +namespace IVD +{ + +namespace Default +{ + +namespace Filter +{ + +int getFontSize(DisplayItem* item) +{ + const auto optional = item->getAttr().getInt(AttributeKey::FontSize); + return optional ? *optional + : Default::FontSize; +} + +FontData loadCustomFont(const std::string path) +{ + //TODO thread-safety? Just lock it if we ever need that. No point in storing + // two copies of the same font, defeating the purpose of the cache, and no + // problem waiting on the other thread to load it, when that's all we'd do anyway. + + //Memory mapping would be better. + static std::map> cachedFonts; + + if(cachedFonts.count(path)) return FontData(&cachedFonts[path][0], cachedFonts[path].size()); + + //Load the fugger... + std::ifstream myFile; + myFile.open(path.c_str(), std::ios::binary); + + if(!myFile) + { + std::cerr << "IVD Runtime: Could not open custom font: " << path << std::endl + << "Falling back on \"sans\"..." << std::endl; + + return FontData(corefontsans, corefontsansSize); + } + + std::vector data((std::istreambuf_iterator(myFile)), + std::istreambuf_iterator()); + + cachedFonts[path] = data; + + return FontData(&cachedFonts[path][0], cachedFonts[path].size()); +} + +FontData getCoreFont(const int fontProperty) +{ + switch(fontProperty) + { + case Property::FontSans: return FontData(corefontsans, corefontsansSize); + case Property::FontSansBold: return FontData(corefontsansbold, corefontsansboldSize); + case Property::FontSansItalic: return FontData(corefontsansitalic, corefontsansitalicSize); + case Property::FontSansBoldItalic: return FontData(corefontsansbolditalic, corefontsansbolditalicSize); + + case Property::FontSerif: return FontData(corefontserif, corefontserifSize); + case Property::FontSerifBold: return FontData(corefontserifbold, corefontserifboldSize); + case Property::FontSerifItalic: return FontData(corefontserifitalic, corefontserifitalicSize); + case Property::FontSerifBoldItalic: return FontData(corefontserifbolditalic, corefontserifbolditalicSize); + + case Property::FontMono: return FontData(corefontmono, corefontmonoSize); + case Property::FontMonoBold: return FontData(corefontmonobold, corefontmonoboldSize); + case Property::FontMonoItalic: return FontData(corefontmonoitalic, corefontmonoitalicSize); + case Property::FontMonoBoldItalic: return FontData(corefontmonobolditalic, corefontmonobolditalicSize); + + default: assert(false); + } +} + +FontData getFontFace(DisplayItem* item) +{ + const auto optionalCustomFont = item->getAttr().getUserToken(AttributeKey::Font); + + if(optionalCustomFont) return loadCustomFont(*optionalCustomFont); + + + const auto optionalFontProperty = item->getAttr().getProperty(AttributeKey::Font); + + return getCoreFont(optionalFontProperty ? *optionalFontProperty + : FontFace); + +} + +static std::string simpleStringFilter(DisplayItem* item, const int key, const std::string def) +{ + auto tt = item->getAttr().getUserToken(key); + if(tt) return *tt; + + auto vk = item->getAttr().getSingleValueKey(key); + if(vk) return item->getEnv()->getString(item, *vk); + + return def; +} + +std::string getTitleText(DisplayItem* item) +{ + return simpleStringFilter(item, AttributeKey::TitleText, TitleText); +} + +std::string getText(DisplayItem* item) +{ + return simpleStringFilter(item, AttributeKey::Text, ""); +} + +Color getFontColor(DisplayItem* item) +{ + const auto optional = item->getAttr().getColor(AttributeKey::FontColor); + + return optional ? *optional + : FontColor; +} + +double getAlpha(DisplayItem* item) +{ + return DefaultAlphaFactor; +} + +std::optional getElementColor(DisplayItem* item) +{ + //Whether it's set or not is important. No color is different from + // alpha. + return item->getAttr().getColor(AttributeKey::ElementColor); +} + +bool getVisibility(DisplayItem* item) +{ + auto vis = item->getAttr().getProperty(AttributeKey::Visibility); + if(vis) return *vis == Property::Enable; + return visible; +} + +bool getFullscreen(DisplayItem* item) +{ + return fullscreen; +} + +bool checkResizable(DisplayItem* item) +{ + //TODO: Also not resizable if fullscreen... But that should be handled by the driver? + auto transAdj = getSizeAdjacent(item); + auto transOpp = getSizeOpposite(item); + + if(transAdj || transOpp) return false; + + auto flag = item->getAttr().getProperty(AttributeKey::Resizable); + + if(!flag) + { + const int sizeStrategy = getWindowSizeStrategy(item); + + if(sizeStrategy == Property::BottomUp) + return false; + else return true; + } + + return *flag == Property::Enable; +} + +bool checkModelOrder(DisplayItem* item) +{ + auto optional = item->getAttr().getProperty(AttributeKey::ModelOrder); + if(!optional) return modelOrder; + return *optional; +} + +std::optional getSizeAdjacent(DisplayItem* item) +{ + return item->getAttr().getInt(AttributeKey::SizeA); +} + +std::optional getSizeOpposite(DisplayItem* item) +{ + return item->getAttr().getInt(AttributeKey::SizeO); +} + +std::optional getPositionWithin(DisplayItem* item) +{ + if(item->getAttr().getSingleValueKey(AttributeKey::PositionWithin)) + { + auto posWith = item->getAttr().getSingleValueKey(AttributeKey::PositionWithin); + + + auto optionalPath = posWith->path; + auto optionalKey = posWith->key; + + if(optionalPath) + return *optionalPath; + + return ValueKeyPath({*posWith->key}); + } + + return std::optional(); +} + +std::optional getAdjacentAlignmentProperty(DisplayItem* item) +{ + return item->getAttr().getProperty(AttributeKey::AlignAdjacent); +} + +std::optional getOppositeAlignmentProperty(DisplayItem* item) +{ + return item->getAttr().getProperty(AttributeKey::AlignOpposite); +} + +Angle getItemFlowAngle(DisplayItem* item) +{ + return Angle::Horizontal; //Ehem... +} + +Angle getRowFlowAngle(DisplayItem* item) +{ + return Angle::Vertical; //EHEM... +} + +int getWindowSizeStrategy(DisplayItem* item) +{ + //Also top-down if fullscreen... TODO + const auto primary = item->getAttr().getProperty(AttributeKey::WindowSizeStrategy); + if(primary) return *primary; + + return WindowSizeStrategy; +} + +std::optional getHorizontalWindowAlignmentProperty(DisplayItem* item) +{ + assert(false); + //return item->getAttr().getProperty(AttributeKey::InitialWindowAlignAdj); +} + +std::optional getVerticalWindowAlignmentProperty(DisplayItem* item) +{ + assert(false); + //return item->getAttr().getProperty(AttributeKey::InitialWindowAlignOpp); +} + +std::optional getJustificationProperty(DisplayItem* item) +{ + return item->getAttr().getProperty(AttributeKey::Justify); +} + + + + +}//Filter +}//Default +}//IVD diff --git a/src/defaults.h b/src/defaults.h new file mode 100644 index 0000000..96d631b --- /dev/null +++ b/src/defaults.h @@ -0,0 +1,104 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef DEFAULTS_H +#define DEFAULTS_H + +#include "color.h" +#include "geometry.h" +#include "valuekey.h" + +#include "keywords.h" + +#include "rustutils/lexcompare.h" + +namespace IVD +{ + +class DisplayItem; + +namespace Default +{ +//Prefer Default::Filter::get*(DisplayItem*) +// to the literals. + +const int FontFace = Property::FontSans; + +const int FontSize = 12; +const Color FontColor = Color(0, 0, 0); //Paint it black +const Color ElementColor = Color(255, 255, 255); +const double DefaultAlphaFactor = 1; + +const std::string TitleText = "IVD Window"; + +const ValueKey MousePath = "IVD-Mouse"; + +const bool visible = true; +const bool fullscreen = false; +const bool resizable = true; +const bool modelOrder = true; + +const std::string quitTrigger = "IVD-Core-Quit"; + +const Angle angle = Angle::Adjacent; +const int alignmentProperty = Property::Inner; + +const int WindowSizeStrategy = Property::TopDown; + +namespace Filter +{ + +int getFontSize(DisplayItem* item); + +struct FontData +{ + const unsigned char* data; + const int size; + + + FontData(const unsigned char* data, const int size): + data(data), + size(size) + {} + + RUSTUTILS_DEFINE_COMP(FontData, data, size); +}; +FontData getFontFace(DisplayItem* item); + +std::string getTitleText(DisplayItem* item); +std::string getText(DisplayItem* item); +//Alignment getAlignment(DisplayItem* item, const int attrkey); +Color getFontColor(DisplayItem* item); +double getAlpha(DisplayItem* item); +std::optional getElementColor(DisplayItem* item); + +bool getVisibility(DisplayItem* item); +bool getFullscreen(DisplayItem* item); + +bool checkResizable(DisplayItem* item); + +bool checkModelOrder(DisplayItem* item); + +Angle getItemFlowAngle(DisplayItem* item); +Angle getRowFlowAngle(DisplayItem* item); + +std::optional getSizeAdjacent(DisplayItem* item); +std::optional getSizeOpposite(DisplayItem* item); + +std::optional getPositionWithin(DisplayItem* item); + +//Why are these optional?? +std::optional getAdjacentAlignmentProperty(DisplayItem* item); +std::optional getOppositeAlignmentProperty(DisplayItem* item); + +std::optional getJustificationProperty(DisplayItem* item); + +std::optional getHorizontalWindowAlignmentProperty(DisplayItem* item); +std::optional getVerticalWindowAlignmentProperty(DisplayItem* item); + +int getWindowSizeStrategy(DisplayItem* item); + +}//Filter +}//Default +}//IVD + +#endif // DEFAULTS_H diff --git a/src/displayitem.cpp b/src/displayitem.cpp new file mode 100755 index 0000000..366c728 --- /dev/null +++ b/src/displayitem.cpp @@ -0,0 +1,469 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "displayitem.h" + +#include "environment.h" +#include "defaults.h" +#include "canvas.h" + +#include "states.h" + +#include "assert.h" + +namespace IVD +{ + +Angle DisplayItem::getRelativeAdjacent() +{ + auto property = getAttr().getProperty(AttributeKey::Orientation); + return !property ? Default::angle + : *property == Property::AdjacentIsHorizontal ? Angle::Horizontal + : Angle::Vertical; +} + +Angle DisplayItem::correctAngle(const Angle theAngle) +{ + //Convert SYMANTIC adjacent into RELATIVE adjacent. + return theAngle == Angle::Adjacent ? getRelativeAdjacent() + : getRelativeOpposite(); +} + +int DisplayItem::getReservedForKey(Angle theAngle, const int adjacentKey, const int oppositeKey) +{ + auto key = getAttr().getInt(theAngle == getRelativeAdjacent() ? adjacentKey + : oppositeKey); + return key ? *key + : 0; +} + +void DisplayItem::addAttributeSet(const AttributePositionPair pair) +{ + contributingAttrs[pair.position] = pair.attrs; + myEnv->markAsChangedAttributes(this); +} + +void DisplayItem::removeAttributeSet(const AttributePositionPair pair) +{ + contributingAttrs.erase(pair.position); + myEnv->markAsChangedAttributes(this); +} + + +void DisplayItem::recomputeAttributeSet() +{ + myAttrs.beginAttributeSetRecompute(); + myAttrs.mergeIn(defaultState); + + for(auto pair : contributingAttrs) + myAttrs.mergeIn(*pair.second); + + myAttrs.commitAttributeSetRecompute(); +} + +void DisplayItem::setParent(DisplayItem* item) +{ + if(item == parent) return; + + deparent(); + parent = item; + parent->children.insert(this); +} + +void DisplayItem::deparent() +{ + if(parent) + { + parent->children.erase(this); + parent = nullptr; + } +} + +void DisplayItem::prepareToDie() +{ + deparent(); + for(DisplayItem* child : children) child->deparent(); +} + +//The root of the tree is *this* if no parents. +DisplayItem* DisplayItem::getRoot() +{ + DisplayItem* root; + DisplayItem* candidate = this; + + while(candidate) + { + root = candidate; + candidate = candidate->parent; + } + + return root; +} + +int DisplayItem::getDepth() +{ + if(parent) return parent->getDepth() + 1; + return 0; +} + +GeometryProposal DisplayItem::reviseProposalAsRequired(GeometryProposal prop) +{ + auto internal = [&](const Angle theAngle) + { + const int attrKey = (theAngle == getRelativeAdjacent()) ? AttributeKey::SizeA + : AttributeKey::SizeO; + + //Goooooooood the interface to Attribute is so fucked up. + if(!getAttr().checkActive(attrKey)) return; + + //TODO wanna remove the binding stuff I think + auto attr = getAttr().getInt(attrKey); + + if(attr && getAttr().isConst(attrKey)) + { + const int requiredSpace = static_cast(*attr); + + prop.proposedDimensions.get(theAngle) = requiredSpace; + prop.expandForAngle(theAngle) = false; + prop.shrinkForAngle(theAngle) = false; + } + else if(attr) //non-const case + { + prop.proposedDimensions.get(theAngle) = int(*attr); + prop.proposedDimensions = prop.roundConflicts(prop.proposedDimensions); + } + }; + + internal(Angle::Horizontal); + internal(Angle::Vertical); + + return prop; +} + +std::optional DisplayItem::getAlignmentProperty(const Angle theAngle) +{ + return getAttr().getProperty(getRelativeAdjacent() == theAngle + ? AttributeKey::AlignAdjacent + : AttributeKey::AlignOpposite); +} + +FillPrecedence DisplayItem::returnGreedyIFEVENONECHILDBLINKS(const Angle theAngle) +{ + const auto& children = getChildren(); + + auto pred = [theAngle](DisplayItem* child) + { + return child->computerFillPrecedenceForAngle(theAngle) == FillPrecedence::Greedy; + }; + return std::any_of(children.begin(), children.end(), pred) ? FillPrecedence::Greedy + : FillPrecedence::Shrinky; + + /// ;; IT'S STILL JUST NOT THE SAAAAAAAAME + /// + /// (if (some (lambda (x) (eql (elt something-something-something x) fill-precedence-greedy)) children) + /// 'fill-precedence-greedy + /// 'fill-precedence-shrinky) + /// + /// ;; I wanne use lithp :< +} + +std::optional DisplayItem::getCellName() +{ + if(getAttr().checkActive(AttributeKey::PositionWithin)) + return std::optional(); + + return getAttr().getSingleValueKey(AttributeKey::PositionWithin)->key; +} + +int DisplayItem::getJustificationOffset(const int itemSize, const int cellSize) +{ + const auto optional = Default::Filter::getJustificationProperty(this); + + if(!optional) return 0; + + switch(*optional) + { + case Property::Inner: return 0; + case Property::Center: return (cellSize ? (cellSize / 2) : 0) - (itemSize ? itemSize / 2 : 0); + case Property::Outer: return cellSize - itemSize; + default: assert(false); + } +} + + + +int DisplayItem::getCellAlignmentOffset(const Angle theAngle, + const int cellSize, + const int viewportSize, + const int reservedInner, + const int reservedOuter) +{ + + const auto optionalProperty = getAlignmentProperty(theAngle); + const int theAlignmentProperty = optionalProperty && (cellSize < viewportSize) ? *optionalProperty + : Default::alignmentProperty; + switch (theAlignmentProperty) + { + case Property::Inner: return reservedInner; + case Property::Center: return (viewportSize ? (viewportSize / 2) : 0) - (cellSize ? cellSize / 2 : 0); + case Property::Outer: return viewportSize - cellSize - reservedOuter; + default: assert(false); + } +} + +Coords DisplayItem::getTranslationOffset() +{ + Coords trans; + + auto optionalTransA = myAttrs.getInt(AttributeKey::TranslationA); + auto optionalTransO = myAttrs.getInt(AttributeKey::TranslationO); + + if(optionalTransA) trans.get(getRelativeAdjacent()) = *optionalTransA; + if(optionalTransO) trans.get(getRelativeOpposite()) = *optionalTransO; + + return trans; +} + +int DisplayItem::getSizeForAngle(const Angle theAngle) +{ + auto optionalSize = [&]() + { + if(correctAngle(theAngle) == Angle::Adjacent) + return myAttrs.getInt(AttributeKey::SizeA); + else + return myAttrs.getInt(AttributeKey::SizeO); + }(); + + return optionalSize ? *optionalSize + : 0; +} + +std::optional DisplayItem::filterFillPrecedenceForAngle(const Angle theAngle) +{ + const auto attrKey = correctAngle(theAngle) == Angle::Adjacent ? AttributeKey::OverrideFillPrecedenceAdjacent + : AttributeKey::OverrideFillPrecedenceOpposite; + const auto optionalAttr = myAttrs.getProperty(attrKey); + + if(optionalAttr) + { + switch(*optionalAttr) + { + case Property::Shrinky: return FillPrecedence::Shrinky; + case Property::Greedy: return FillPrecedence::Greedy; + default: std::terminate(); //Prolly a little drastic... TODO + } + } + + return std::optional(); +} + +FillPrecedence DisplayItem::computerFillPrecedenceForAngle(const Angle theAngle) +{ + const auto optionalOverride = filterFillPrecedenceForAngle(theAngle); + + if(optionalOverride) return *optionalOverride; + + if(myWidget.checkIsSet()) + return myWidget.getFillPrecedence(theAngle); + + return FillPrecedence::Shrinky; //DEFAULT +} + +void DisplayItem::shape(const GeometryProposal officialProposal) +{ + GeometryProposal revisedProposal = reviseProposalAsRequired(officialProposal); + + revisedProposal.proposedDimensions -= getReservedDimens(); + + if(revisedProposal.proposedDimensions.w < 0) + revisedProposal.proposedDimensions.w = 0; + + if(revisedProposal.proposedDimensions.h < 0) + revisedProposal.proposedDimensions.h = 0; + + const Dimens proposedCellSpace = shapeDrawingArea(revisedProposal) + getReservedDimens(); + + myCellDimens = proposedCellSpace; + myViewportDimens = officialProposal.roundConflicts(proposedCellSpace); + compliantGeometry = officialProposal.verifyCompliance(myCellDimens); + + //Cell alignment + relativeCellOffset = getCellAlignmentOffsetMindingReserved(myCellDimens, myViewportDimens); +} + +Dimens DisplayItem::shapeDrawingArea(const GeometryProposal officalProposal) +{ + if(myWidget.checkIsSet()) + { + myWidget.shape(officalProposal); + return myWidget.getSpace(); + } + else return officalProposal.proposedDimensions; +} + +void DisplayItem::computerAbsoluteOffsets(const Coords parentViewportOffset) +{ + absoluteViewportOffset = parentViewportOffset + relativeViewportOffset; + absoluteCellOffset = absoluteViewportOffset + relativeCellOffset; + + for(DisplayItem* child : children) + child->computerAbsoluteOffsets(absoluteViewportOffset); +} + +void DisplayItem::render() +{ + Canvas* theCanvas = myEnv->getCanvas(); + const Rect viewportClip(absoluteViewportOffset, myViewportDimens); + //Cell offset already takes margins into account + const Rect cellClip(absoluteCellOffset, myCellDimens); + const Rect borderLessCell = [&]() -> Rect + { + Rect clip = cellClip; + clip.d -= getReservedBorderDimens(); + clip.c += getReservedInnerBorderDimens(); + return clip; + }(); + + const Rect contentClip = [&]() -> Rect + { + Rect clip = borderLessCell; + clip.d -= getReservedPaddingDimens(); + clip.c += getReservedInnerPaddingDimens(); + return clip; + }(); + + const auto savedAlpha = theCanvas->getAlpha(); + theCanvas->setAlpha(savedAlpha * Default::Filter::getAlpha(this)); + + theCanvas->pushClip(viewportClip); + theCanvas->pushClip(cellClip); //they could overlap + + + //Draw boxeee + { + //--->Draw borders<--- + + theCanvas->pushClip(borderLessCell); + + const auto elementColor = Default::Filter::getElementColor(this); + if(elementColor) + theCanvas->fillRect(borderLessCell, *elementColor); + + theCanvas->popClip(); //borderlessCell + } + + if(myWidget.checkIsSet() && myWidget.isDrawable()) + { + theCanvas->pushClip(contentClip); + theCanvas->setOffset(contentClip.c); + + myWidget.draw(myWidget.isLayout() ? nullptr : theCanvas); + + theCanvas->resetOffset(); + theCanvas->popClip(); //contentClip + } + + theCanvas->popClip();//cellClip + theCanvas->popClip();//viewportClip + theCanvas->setAlpha(savedAlpha); //restored alpha :) +} + +void DisplayItem::updateHover() +{ + const Coords point = myEnv->getMouseOffsetRelativeToWindow(); + + if(Rect(absoluteCellOffset, myCellDimens).checkCollision(point)) + { + if(myWidget.checkIsSet()) + { + myWidget.bubble(); + + //Should this have direct integration or just let + // the widget do state stuff? I think the latter... + //So... Why does it have a return value TODO + const Coords relativePoint = point - absoluteCellOffset + getReservedInnerPaddingDimens(); + myWidget.detectCollisionPoint(relativePoint); //Rename to "handle"? + } + //else without a layout/widget we just assume there's no rules for child collision + // because layouts define all that stuff + + //Now we handle our own + if(!theStateManager->checkAny(StateKey(States::Item::HoverExclusive))) //horribly inefficient + theStateManager->mutateIfObserved(StateKey(States::Item::HoverExclusive, this), true); + + theStateManager->mutateIfObserved(StateKey(States::Item::HoverInclusive, this), true); + } +} + +std::vector DisplayItem::getChildWidgetInStampOrder() +{ + std::vector sorted; + for(DisplayItem* child : children) + sorted.push_back(child); + + auto compareStamp = [&](DisplayItem* left, DisplayItem* right) + { + return left->getElementStamp() < right->getElementStamp(); + }; + std::sort(sorted.begin(), sorted.end(), compareStamp); + + //---------------------->stable SORT BY NAMED CELLS<------ + //Just reverse iterate the named cells list, and then sub-iteratorate + // the result vector, and move the one to the beginning. + //YES I KNOW IT'S SUPER INNEFICIENT. But it might be better + // than a clever solution because named cells will probably never be + // more than like 10, and the children should typically be == to named cells + // so 10*10 isn't a huge deal + //It's probably not even worth testing unless this shows up in during + // profiling. + + if(myAttrs.checkActive(AttributeKey::CellNames)) + { + auto cells = myAttrs.getLiteralList(AttributeKey::CellNames); + + for(std::vector::reverse_iterator rit = cells.rbegin(); rit != cells.rend(); ++rit) + { + const std::string& cellName = *rit; + + for(std::vector::iterator it = sorted.begin(); it != sorted.end(); ++it) + { + DisplayItem* child = *it; + if(child->getCellName() == cellName) + { + sorted.erase(it); + sorted.insert(sorted.begin(), child); + break; + } + } + } + } + + + //Then do a special "first"/"last" reorder if you want that feature + // back for whatever reason. + + std::vector result; + + for(DisplayItem* child : sorted) + result.push_back(reinterpret_cast(child)); + + return result; +} + +IVD_Element* DisplayItem::getChildElementForNamedCell(const std::string name) +{ + for(DisplayItem* child : children) + { + auto optionalCellName = getCellName(); + + if(!optionalCellName) continue; + + if(*optionalCellName == name) + return reinterpret_cast(child); + } + + return nullptr; +} + + + +}//IVD diff --git a/src/displayitem.h b/src/displayitem.h new file mode 100755 index 0000000..2a567d1 --- /dev/null +++ b/src/displayitem.h @@ -0,0 +1,309 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include +#include + +#include "attributepositionpair.h" +#include "runtimeattributeset.h" +#include "widget.h" + +#include + +namespace IVD +{ + +class Environment; +class ModelContainer; +class StateManager; +class Canvas; + + +class DisplayItem +{ + const int elementStamp; + Element* myElem; + Environment* myEnv; + StateManager* theStateManager; + DisplayItem* parent; + + const ValueKeyPath elementPath; + + const ReferenceAttributeSet defaultState; + std::map contributingAttrs; + RuntimeAttributeSet myAttrs; + + //Cell is the actual box we want, adjusted for margins? + //Viewport is the space we have to position in. + bool compliantGeometry = false; + Dimens myCellDimens; + Dimens myViewportDimens; //We need the viewport for the clip + + Coords relativeViewportOffset; //Relative to parent + Coords relativeCellOffset; //Relative to viewport + + //These are very invalid during shaping + Coords absoluteViewportOffset; + Coords absoluteCellOffset; + + + WidgetWrapper myWidget; + + Canvas* theCanvas = nullptr; + + std::set children; + + std::map variableMap; + + Angle getRelativeAdjacent(); + Angle getRelativeOpposite() + { return getRelativeAdjacent() == Angle::Horizontal ? Angle::Vertical + : Angle::Horizontal; } + + Angle correctAngle(const Angle theAngle); + + int getReservedForKey(Angle theAngle, const int adjacentKey, const int oppositeKey); + + std::optional getAlignmentProperty(const Angle theAngle); + + + //putting this here cause I wanna save it but don't know where to put + // it yet. Probably gonna be placed beyond the C boundary as a helper + // eventually. + FillPrecedence returnGreedyIFEVENONECHILDBLINKS(const Angle theAngle); + + std::optional getCellName(); + +public: + DisplayItem(Element* elem, + Environment* theEnv, + StateManager* theStateManager, + const ReferenceAttributeSet& theDefaultState, + const ValueKeyPath &elemPath, + const int elementStamp): + myElem(elem), + myEnv(theEnv), + parent(nullptr), + elementPath(elemPath), + elementStamp(elementStamp), + defaultState(theDefaultState), + theStateManager(theStateManager), + myAttrs(this, theDefaultState) + { + reprodyne_open_scope(this); + } + + Element* getElement() + { return myElem; } + + void destroyWidget() + { myWidget.destroy(); } + + IVD_Widget* setupNewWidget(const WidgetBlueprints blueprints) + { + myWidget.reset(myEnv, blueprints); + return myWidget.get(); + } + + IVD_Widget* getWidget() + { return myWidget.get(); } + + void reactToTrigger(const std::string trigg) + { myWidget.handleTrigger(trigg); } + + int getElementStamp() { return elementStamp; } + + //TODO: warn if the variable isn't found. Don't take that shit from noone. + void setVariable(const ValueKey key, const double val) + { variableMap[key] = val; } + + double getVariable(const ValueKey key) + { return variableMap[key]; } + + Environment* getEnv() + { return myEnv; } + + void addAttributeSet(const AttributePositionPair pair); + void removeAttributeSet(const AttributePositionPair pair); + void recomputeAttributeSet(); + + RuntimeAttributeSet& getAttr() + { return myAttrs; } + + void setParent(DisplayItem* item); + void deparent(); + DisplayItem* getParent() + { return parent; } + + void prepareToDie(); + + bool hasParent() + { return parent; } + DisplayItem* getRoot(); + + ValueKeyPath getElementPath() + { return elementPath; } + + int getDepth(); + + //TODO: Borderssssssssssssssssssss + + int getInnerMargin(Angle theAngle) + { return getReservedForKey(theAngle, AttributeKey::MarginAdjIn, AttributeKey::MarginOppIn); } + + int getOuterMargin(Angle theAngle) + { return getReservedForKey(theAngle, AttributeKey::MarginAdjOut, AttributeKey::MarginOppOut); } + + int getInnerPadding(Angle theAngle) + { return getReservedForKey(theAngle, AttributeKey::PaddingAdjIn, AttributeKey::PaddingOppIn); } + + int getOuterPadding(Angle theAngle) + { return getReservedForKey(theAngle, AttributeKey::PaddingAdjOut, AttributeKey::PaddingOppOut); } + + int getReservedMargin(Angle theAngle) + { return getInnerMargin(theAngle) + getOuterMargin(theAngle); } + + int getReservedPadding(Angle theAngle) + { return getInnerPadding(theAngle) + getOuterPadding(theAngle); } + + int getReservedInner(Angle theAngle) + { return getInnerMargin(theAngle) + getInnerPadding(theAngle); } + + int getReservedOuter(Angle theAngle) + { return getOuterMargin(theAngle) + getOuterPadding(theAngle); } + + int getReserved(Angle theAngle) + { return getReservedInner(theAngle) + getReservedOuter(theAngle); } + + Dimens getReservedInnerMarginDimens() + { return Dimens(getInnerMargin(getRelativeAdjacent()), + getInnerMargin(getRelativeOpposite())); } + + Dimens getReservedInnerPaddingDimens() + { return Dimens(getInnerPadding(getRelativeAdjacent()), + getInnerPadding(getRelativeOpposite())); } + + Dimens getReservedOuterMarginDimens() + { return Dimens(getOuterMargin(getRelativeAdjacent()), + getOuterMargin(getRelativeOpposite())); } + + Dimens getReservedOuterPaddingDimens() + { return Dimens(getOuterPadding(getRelativeAdjacent()), + getOuterPadding(getRelativeOpposite())); } + + Dimens getReservedPaddingDimens() + { return getReservedInnerPaddingDimens() + getReservedOuterPaddingDimens(); } + + Dimens getReservedMarginDimens() + { return getReservedInnerMarginDimens() + getReservedOuterMarginDimens(); } + + Dimens getReservedInnerDimens() + { return Dimens(getReservedInner(getRelativeAdjacent()), + getReservedInner(getRelativeOpposite())); } + + Dimens getReservedOuterDimens() + { return Dimens(getReservedOuter(getRelativeAdjacent()), + getReservedOuter(getRelativeOpposite())); } + + Dimens getReservedDimens() + { return getReservedInnerDimens() + getReservedOuterDimens(); } + + Dimens getReservedInnerBorderDimens() + { return Dimens(); } + + Dimens getReservedOuterBorderDimens() + { return Dimens(); } + + Dimens getReservedBorderDimens() + { return getReservedInnerBorderDimens() + getReservedOuterBorderDimens(); } + + GeometryProposal reviseProposalAsRequired(GeometryProposal prop); + + bool checkCellAlignmentOffset(const Angle theAngle) + { return (bool)getAlignmentProperty(theAngle); } + + int getJustificationOffset(const int itemSize, const int cellSize); + int getCellAlignmentOffset(const Angle theAngle, + const int cellSize, + const int viewportSize, + const int reservedInner, + const int reservedOuter); + + int getCellAlignmentOffset(const Angle theAngle, const int cellSize, const int viewportSize) + { + return getCellAlignmentOffset(theAngle, + cellSize, + viewportSize, + getReservedInner(theAngle), + getReservedOuter(theAngle)); + } + + Coords getCellAlignmentOffset(const Dimens cellSize, + const Dimens viewportSize, + const Dimens reservedInner, + const Dimens reservedOuter) + { + auto get = [&](const Angle theAngle) + { + return getCellAlignmentOffset(theAngle, + cellSize.get(theAngle), + viewportSize.get(theAngle), + reservedInner.get(theAngle), + reservedOuter.get(theAngle)); + }; + + return Coords(get(Angle::Horizontal), get(Angle::Vertical)); + } + + Coords getCellAlignmentOffsetMindingReserved(const Dimens cellSize, + const Dimens viewportSize) + { + return getCellAlignmentOffset(cellSize, + viewportSize, + getReservedInnerDimens(), + getReservedOuterDimens()); + } + + Coords getTranslationOffset(); + + int getSizeForAngle(const Angle theAngle); + + std::optional filterFillPrecedenceForAngle(const Angle theAngle); + + void setOffset(const Coords offset) + { relativeViewportOffset = offset; } + + Dimens getViewportDimens() const + { return myViewportDimens; } + + FillPrecedence computerFillPrecedenceForAngle(const Angle theAngle); + void shape(const GeometryProposal officialProposal); + Dimens shapeDrawingArea(const GeometryProposal officalProposal); + + //This figures the absolute offset for the viewport, not the cell! + //The relative offset for the viewport, is the offset relative to the parent + // layout. + void computerAbsoluteOffsets(const Coords parentViewportOffset); + + //Canvas should always be set before call to render + void render(); + + void updateHover(); + + //I wish the reference for the container could be const, + // without that propogating to the pointers... + const std::set& getChildren() + { return children; } + + std::vector getChildWidgetInStampOrder(); + + IVD_Element* getChildElementForNamedCell(const std::string name); + + + const int childCount() + { return children.size(); } +}; + + +}//IVD diff --git a/src/driver.h b/src/driver.h new file mode 100644 index 0000000..32e9a53 --- /dev/null +++ b/src/driver.h @@ -0,0 +1,53 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef DRIVER_H +#define DRIVER_H + +#include "geometry.h" + +namespace IVD +{ + +class DisplayItem; +class StateManager; +class Canvas; + +class Driver +{ + StateManager* myStateManager; + +protected: + StateManager* getStateManager() + { return myStateManager; } + +public: + Driver(): myStateManager(nullptr) {} + virtual ~Driver() {} + + void setStateManager(StateManager* theStateManager) + { myStateManager = theStateManager; } + + virtual void addDisplayItem(DisplayItem* item) = 0; + virtual void removeDisplayItem(DisplayItem* item) = 0; + virtual void processEvents() = 0; + + virtual bool checkHoverInvalidated() = 0; + virtual DisplayItem* getWindowItemWithMouseFocus() = 0; + virtual Coords getMousePointRelativeToWindow() = 0; + + virtual void invalidateGeometry(DisplayItem* item) = 0; + virtual void invalidatePosition(DisplayItem* item) = 0; + virtual void invalidateCanvas(DisplayItem* item) = 0; + virtual void invalidateTitleText(DisplayItem* item) = 0; + virtual void invalidateVisibility(DisplayItem* item) = 0; + + virtual Canvas* getCanvas() = 0; + + virtual void refresh() = 0; + + virtual bool checkAnythingToDo() = 0; +}; + +} + +#endif // DRIVER_H diff --git a/src/element.h b/src/element.h new file mode 100755 index 0000000..3ed8c74 --- /dev/null +++ b/src/element.h @@ -0,0 +1,139 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef ELEMENT_H +#define ELEMENT_H + +#include +#include + +#include "assert.h" + +#include "statekey.h" +#include "referenceattributeset.h" +#include "attributepositionpair.h" +#include "virtualstatekey.h" + +namespace IVD +{ + +class DisplayItem; + + +//Element probably isn't the best term for this, because +// they're still essentially templates for instances of +// DisplayItems. +class Element +{ + int elementStamp = -42; + ValueKeyPath path; + ValueKeyPath modelPath; + + ReferenceAttributeSet defaultAttr; + + std::vector keyedAttributes; + std::map keyedAttributeMap; + + std::vector virtualKeys; + + std::map variableInitialExpressions; + + //I have no idea what this is doing anymore + int obtainPosForKey(const ScopedValueKey key) + { + auto it = keyedAttributeMap.find(key); + + if(it == keyedAttributeMap.end()) + { + const int pos = keyedAttributes.size(); + keyedAttributes.emplace_back(defaultAttr.size()); + keyedAttributeMap[key] = pos; + + return pos; + } + + return it->second; + } + +public: + Element(const int stamp, const int attributeCount): + elementStamp(stamp), + defaultAttr(attributeCount) + {} + + int getElementStamp() { return elementStamp; } + + const ValueKeyPath& getPath() const + { return path; } + + void setPath(const ValueKeyPath& theName) + { path = theName; } + + void setModelPath(ValueKeyPath key) + { modelPath = key; } + + void applyToEachScopedValueKey(std::function fun) + { + defaultAttr.applyToEachScopedValueKey(fun); + for(auto& attr : keyedAttributes) attr.applyToEachScopedValueKey(fun); + } + + void addVirtualStateKey(VirtualStateKeyPrecursor vskey) { virtualKeys.push_back(vskey); } + + void setInitialExpression(const ValueKey key, Expression initial) + { variableInitialExpressions[key] = initial; } + + auto getVariableInitialExpressions() + { return variableInitialExpressions; } + + std::vector getVirtualKeys() { return virtualKeys; } + ReferenceAttributeSet& getDefaultAttr() { return defaultAttr; } + std::map getKeyedAttributeMap() { return keyedAttributeMap; } + ValueKeyPath getModelPath() const { return modelPath; } + + ReferenceAttributeSet& getAttributeSetForStateKey(ScopedValueKey statePre) + { + if(!statePre.key) return defaultAttr; + + const int pos = obtainPosForKey(statePre); + return keyedAttributes.at(pos); + } + + AttributePositionPair at(const int pos) + { return AttributePositionPair(pos, &keyedAttributes.at(pos)); } + + void deriveFrom(Element& parentClass) + { + defaultAttr.deriveFrom(parentClass.defaultAttr); + + for(const auto& pair : parentClass.keyedAttributeMap) + { + const auto key = pair.first; + const int myPos = obtainPosForKey(key); + const int otherPos = parentClass.obtainPosForKey(key); + + keyedAttributes.at(myPos).deriveFrom(parentClass.keyedAttributes.at(otherPos)); + } + + //ALRIGHT SO + //This is broken TODO + //I don't remember when I broke it, but it's been broken since I broke IVD off + // from the original repo. + //I know it used to work because there is still a validation test that some ancient + // build produced correctly. + //I spent a while trying to track this down, thinking I recently broke something and + // there was some sort of missing ampersand or something, but nope. Been like this since + // the initial commit. + //I'm not going to do anything about it right now because obviously things were (mostly) + // working before I started the refactor I'm at the tail-end of now, and I just wanna get + // back to that state first. I'm leaving this comment so that I don't waste time in the future. + //Basically, the below line doesn't work because we want to merge states that have equivalent + // virtual keys. This is non-trivial, obviously. + //I vaguely remember deleting the code thinking it was kludgy? I don't remember why. Or if I didn't + // realize what I was doing. But yeaaaah it doesn't work. + //for(auto& vk : parentClass.virtualKeys) virtualKeys.push_back(vk); + } +}; + +}//IVD + +#endif // ELEMENT_H diff --git a/src/environment.cpp b/src/environment.cpp new file mode 100755 index 0000000..27ff8fa --- /dev/null +++ b/src/environment.cpp @@ -0,0 +1,760 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "environment.h" +#include "displayitem.h" +#include "states.h" +#include "defaults.h" +#include "driver.h" + +#include + +#include "user_include/IVD_status.h" + +#include +#include +#include + +namespace IVD +{ + + +Driver* createDefaultDriver(); + +Environment::Environment(): + managedDriver(createDefaultDriver()), + myDriver(managedDriver.get()) +{ + initOthers(); +} + +void Environment::initOthers() +{ + managedDriver->setStateManager(&myStateManager); +} + +StateKey Environment::generateStateKeyFromPrecursor(ScopedValueKey precursor, DisplayItem* baseContext) +{ + assert(precursor.key); + + StateKey myStateKey; + myStateKey.identity = *precursor.key; + + if(precursor.myScope == ScopedValueKey::Scope::Element) + myStateKey.scope = baseContext; + else if(precursor.myScope == ScopedValueKey::Scope::Model) + { + assert(false); + } + //Global is default, scope is nullptr already. + + if(precursor.path) + { + auto optionalTarget = deduceTarget(baseContext, *precursor.path); + assert(optionalTarget); + myStateKey.scope = *optionalTarget; + } + + return myStateKey; +} + +void Environment::processDeferredVirtualStates() +{ + for(auto vskeyp : deferredVirtualStateKeys) + { + VirtualStateKey vskey = vskeyp.generateVirtualStateKey(this); + myStateManager.insertVirtualState(vskey); + } + + deferredVirtualStateKeys.clear(); +} + +void Environment::processDeferredPositioning() +{ + for(auto pair : deferredPositioning) + positionDisplayItemInDrawTree(pair.item, pair.parent); + processDeferredVirtualStates(); +} + +DisplayItem* Environment::setupNewDisplayItem(Element* elem) +{ + DisplayItem* item = nullptr; + { + std::unique_ptr itemUniquePtr = + std::make_unique(elem, + this, + &myStateManager, + elem->getDefaultAttr(), + elem->getPath(), + elem->getElementStamp()); + + item = itemUniquePtr.get(); + + instances[item] = std::move(itemUniquePtr); + } + + elementToDisplayItems[elem].insert(item); + + for(auto val : elem->getKeyedAttributeMap()) + { + const auto preKey = val.first; + const auto pos = val.second; + const auto pair = elem->at(pos); + + myStateManager.registerStateObserver(generateStateKeyFromPrecursor(preKey, item), item, pair); + } + + for(VirtualStateKeyPrecursor vskeyp : elem->getVirtualKeys()) + { + vskeyp.context = item; + deferredVirtualStateKeys.push_back(vskeyp); + + //These are deferred, because they might reference other states that haven't + // been registered yet, and they'll miss the hook otherwise. Items are instantiated + // in batches in lots of cases, the batches having the necessary states. + //We can't even create the virtualstatekey, because it may reference states + // that we haven't *inserted* yet. + //Is this still true after the model rewrite? ~~ feb 2021 + } + + for(auto pair : elem->getVariableInitialExpressions()) + item->setVariable(pair.first, pair.second.solve(item)); + + itemsWithChangedAttributeSets.insert(item); + return item; +} + +void Environment::destroyDisplayItem(DisplayItem* item) +{ + DisplayItem*& youKillMyFather = item; + + + + //Hello, my name is Inigo Montoya, + youKillMyFather->prepareToDie(); + + + assert(!triggerMap.count(item)); + + + //A word on widgets. + //Any widget item that references this item is about to be destroyed anyway, + // if there is a widget, it's removal is what triggered this. So don't worry + // about it's references. + + + itemsWithChangedAttributeSets.erase(item); + myStateManager.removeReferencesToDisplayItem(item); + + elementToDisplayItems.at(item->getElement()).erase(item); + instances.erase(item); //Don't you just love unique_ptr? +} + +void Environment::positionDisplayItemInDrawTree(DisplayItem* item, IVD_Widget* parentWidget = nullptr) +{ + if(!parentWidget && widgetToDisplayItem.count(item->getWidget())) + return; //permanent custody + + const auto posPath = Default::Filter::getPositionWithin(item); + + if(posPath && posPath->size() && posPath->at(0) == "Environment") + { + if(item->getParent()) + { + myDriver->invalidateGeometry(item->getParent()); + item->deparent(); + } + + myDriver->addDisplayItem(item); + myDriver->invalidateGeometry(item); + return; + } + + myDriver->removeDisplayItem(item); + + if(parentWidget) + { + DisplayItem* parentItem = widgetToDisplayItem.at(parentWidget); + item->setParent(parentItem); + markAsBadGeometry(parentItem); + } + else if(posPath) + { + ValueKeyPath path = *posPath; + auto target = deduceTarget(item, path); + + if(!target) + { + std::cerr << "IVD Runtime: Could not position [" << item->getElementPath() << "] "; + + if(widgetToDisplayItem.count(item->getWidget())) + std::cerr << "-> [Widget] "; + + std::cerr << "within [" << path << "] " + << "-> [Widget/Static], could not deduce parent." + << std::endl; + return; + } + + if(item->getParent()) + myDriver->invalidateGeometry(item->getParent()); + + item->setParent(*target); + myDriver->invalidateGeometry(*target); + } + else + { + myDriver->invalidateGeometry(item->getParent()); + item->deparent(); + } +} + +void Environment::setWidget(DisplayItem *item) +{ + if(widgetToDisplayItem.count(item->getWidget())) + return; //it be bound by BLOOD + + item->destroyWidget(); + + WidgetBlueprints blueprints; + + if(auto optionalLayoutName = item->getAttr().getUserToken(AttributeKey::Layout)) + { + blueprints = layoutBlueprints.at(*optionalLayoutName); + } + else if(auto optionalWidgetName = item->getAttr().getUserToken(AttributeKey::Widget)) + { + blueprints = widgetBlueprints.at(*optionalWidgetName); + } + else return; //Otherwise, we leave it blank, because it's not actually needed ^^ + + widgetToDisplayItem[item->setupNewWidget(blueprints)] = item; +} + +std::optional Environment::deduceTarget(DisplayItem *context, const ValueKeyPath key) +{ + //This used to be a lot more sophisticated which is why it's kinda weird looking now + // but leaving it here because I'm still unsure of the future direction. + + std::optional result; + + Element* targetElem = elementLookupByPath[key]; + const auto count = elementToDisplayItems.at(targetElem).size(); + + if(count > 1) + std::cout << "IVD Runtime Warning: Target Ambiguous: " << key << std::endl; + + //Grab the first + for(DisplayItem* target : elementToDisplayItems.at(targetElem)) + return target; + + return result; +} + +double Environment::commonExternalAccessor(DisplayItem* context, + const ScopedValueKey key, + std::optional value) +{ + if(key.myScope == ScopedValueKey::Scope::Model) + { + std::cerr << "Warning dead code path XXX" << std::endl; + return 42; + } + + + //And NOW it can be a material function... But only on context + assert(key.myScope == ScopedValueKey::Scope::Element); + + + assert(key.key); + + DisplayItem* other = nullptr; + + if(!key.path) other = context; //Self referential. + else + { + auto result = deduceTarget(context, *key.path); + + assert(result); + + other = *result; + } + + if(auto optional = myComp.getSymbolForLiteral(*key.key)) + { + if(value) other->getAttr().setDeclaredInt(*key.key, *value); + else return *context->getAttr().getDeclaredInt(*key.key); + } + else //User variable + { + if(value) context->setVariable(*key.key, *value); + else return context->getVariable(*key.key); + } + + //If we change anything... Yeah. + markAsBadGeometry(context); + + return 0; +} + +void Environment::markAsBadGeometry(DisplayItem *item) +{ + myDriver->invalidateGeometry(item); +} + +void Environment::markAsBadCanvas(DisplayItem *item) +{ + myDriver->invalidateCanvas(item); +} + +void Environment::updateHover() +{ + myStateManager.mutateAll(States::Item::HoverExclusive, false); + myStateManager.mutateAll(States::Item::HoverInclusive, false); + + DisplayItem* root = myDriver->getWindowItemWithMouseFocus(); + if(!root) return; + + root->updateHover(); +} + +Coords Environment::getMouseOffsetRelativeToWindow() +{ return myDriver->getMousePointRelativeToWindow(); } + +void Environment::run() +{ + while(true) + { + reprodyne_mark_frame(); + + processDeferredPositioning(); + + const uint64_t lastStateStampBeforeLoop = myStateManager.getLastStamp(); + + { + auto mySet = itemsWithChangedAttributeSets; + itemsWithChangedAttributeSets.clear(); + + for(DisplayItem* item : mySet) + { + item->recomputeAttributeSet(); + item->getAttr().executeStateChangers(); + item->getAttr().fireSets(); + } + } + + + for(AnimatableAttribute* a : attributeWantsAnimationTick) + { + //There's a bug in GCC's std::set that I haven't been able to reliably reproduce to report. + // (Aggressive compiler optimizations probably makes isolation difficult...) + //But without this conditional, it crashes -somtimes-. + if(!attributeWantsAnimationTick.size()) break; + a->animationTick(); + } + //HAS to come after recomputeAttributeSet, which accepts changes + myStateManager.resetTriggerStates(); + + //------------------------------------------------------------Draw tree frozen beyond this point + + //These take precedence because the others will reference windows that otherwise + // don't exist... + + { + auto myDrawTreeMutatingAttributes = attributesThatMutateTheDrawTree; + attributesThatMutateTheDrawTree.clear(); + for(AnimatableAttribute* attr : myDrawTreeMutatingAttributes) + { + attr->executeChangeAcceptor(); + } + + //Do the do + auto myAttributesChanged = attributesThatHaveChanged; + attributesThatHaveChanged.clear(); + for(AnimatableAttribute* attr : myAttributesChanged) + { + attr->executeChangeAcceptor(); + } + } + + { + bool die = false; + for(auto it : triggerMap) + { + DisplayItem* item = it.first; + for(auto triggerKey : it.second) //Usually only one, but w/e + { + if(triggerKey.myScope == ScopedValueKey::Scope::Model) + item->reactToTrigger(*triggerKey.key); + + //Elemental scope means nothing... But it's the default if nothing else is specified. + //Soo... We just assume anything that's not model is global. It's hacky, but eh. + //It works for now without reworking part of the compiler... + else + { + if(triggerKey.key == Default::quitTrigger) + die = true; //Guarantee that all triggers get called. Safe shutdown, etc... + //else trigger unrecognized + } + } + } + triggerMap.clear();//Mother FUCKER + if(die) break; + } + + myDriver->processEvents(); + if(myDriver->checkHoverInvalidated()) updateHover(); + myDriver->refresh(); //<--- This is expected to handle reprodyne video frame validation + + if(!attributeWantsAnimationTick.size() && + !myDriver->checkAnythingToDo() && + lastStateStampBeforeLoop == myStateManager.getLastStamp()) + { + //Sleepy pea. + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + } +} + +int Environment::loadFromIVDFile(const char* path) +{ + if(!myComp.compileFile(path)) return IVD_STATUS_FILE_NOT_FOUND; + if(myComp.getErrorMessages().size()) return IVD_STATUS_COMPILE_ERROR; + + for(Element& elem : myComp.getElements()) + { + elementLookupByPath[elem.getPath()] = &elem; + + if(elem.getModelPath().size()) + { + //DIRTY HAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + //FIX THE COMPILER + elementModelLookup[elem.getModelPath()[0]] = &elem; + + //Defer actual instantiation until the main loop, as it's a continuous process. + continue; + } + + //Spin-up static elements + setupNewDisplayItem(&elem); + } + + processDeferredVirtualStates(); + return IVD_STATUS_SUCCESS; +} + +IVD_Widget* Environment::createWidget(const std::string name, IVD_Widget* parent) +{ + DisplayItem* item = setupNewDisplayItem(elementModelLookup[name]); + IVD_Widget* widget = item->setupNewWidget(widgetBlueprints.at(name)); + + widgetToDisplayItem[widget] = item; + + //We have to defer parenting because otherwise + // the children potentially created in the constructor + // of widget will be trying to position in a parent not + // yet registered in userOwnedWidgets. + deferredPositioning.push_back({item, parent}); +} + +IVD_Element* Environment::createIVDelementFromClass(const std::string className, IVD_Widget* parent) +{ + Element* classElement = myComp.getElementForClass(className); + if(!classElement || !parent) + { + //todo runtime error + return nullptr; + } + + DisplayItem* item = setupNewDisplayItem(classElement); + deferredPositioning.push_back({item, parent}); + + return reinterpret_cast(item); +} + +void Environment::destroyWidget(IVD_Widget* widget) +{ + DisplayItem* item = widgetToDisplayItem.at(widget); + widgetToDisplayItem.erase(widget); + + if(widgetOwnedDisplayItems.count(widget)) + { + for(DisplayItem* item : widgetOwnedDisplayItems.at(widget)) + destroyDisplayItem(item); + } + + markAsBadGeometry(item); //okayyyy is this safe AT ALL?? With models there was a comment about root windows XXX + destroyDisplayItem(item); //Calls the dtor in the blueprints. + //Unlike construction, child destruction should be straight forward. +} + +void Environment::destroyIVDelement(IVD_Widget* parent, IVD_Element* elem) +{ + if(!parent) + { + //todo + std::terminate(); //HOLD IT RIGHT THERE + } + + DisplayItem* item = reinterpret_cast(elem); + widgetOwnedDisplayItems.at(parent).erase(item); + + markAsBadGeometry(item); + destroyDisplayItem(item); +} + +Canvas* Environment::getCanvas() +{ return myDriver->getCanvas(); } + + +double Environment::getInteger(DisplayItem* context, const ScopedValueKey key) +{ + if(key.myScope == ScopedValueKey::Scope::Global && + key.path && + key.path->size() == 1 && + key.path->front() == Default::MousePath) + { + assert(key.key); + + //Should this be relative to the item or the window? + // by default it's relative to window... TODO + if(key.key == myComp.getLiteralForSymbol(AttributeKey::TranslationO)) + return myDriver->getMousePointRelativeToWindow().x; + if(key.key == myComp.getLiteralForSymbol(AttributeKey::TranslationA)) + return myDriver->getMousePointRelativeToWindow().y; + //TODO needs scroll distance and there is no keyword for it. + //Not a mouse + } + + return commonExternalAccessor(context, key, std::optional()); +} + +std::string Environment::getString(DisplayItem* context, const ScopedValueKey key) +{ + return "Dead code path"; +} + +void Environment::setupEnvironmentCallbacksOnAttributeForKey(AnimatableAttribute* attr, const int key) +{ + //Maybe check if even active? + + //Bleh, these should all be stored lambdas, and just send a reference to the attributes....... + using namespace AttributeKey; + attr->setAnimationTickRequester([&](AnimatableAttribute* attr) + { attributeWantsAnimationTick.insert(attr); }); + + attr->setCancelAnimationTicker([&](AnimatableAttribute* attr) + { attributeWantsAnimationTick.erase(attr); }); + + if(key == PositionWithin) + { + attr->setSignalChangedAttribute([&](AnimatableAttribute* attr) + { attributesThatMutateTheDrawTree.insert(attr); }); + } + else + { + attr->setSignalChangedAttribute([&](AnimatableAttribute* attr) + { attributesThatHaveChanged.insert(attr); }); + } + + //Just a little helper for the state changing attributes~ + auto applyToStates = [&](AnimatableAttribute* attr, std::function fun) + { + auto states = attr->getValueKeyList(); + if(!states.size()) return; //TODO Should this... EVER be possible? Assert here fails. + + for(const ScopedValueKey pre : states) + { + const StateKey key = generateStateKeyFromPrecursor(pre, attr->revealContext()); + fun(key); + } + }; + + + switch(key) + { + case PositionWithin: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + positionDisplayItemInDrawTree(attr->revealContext()); + }); + break; + + case TitleText: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + myDriver->invalidateTitleText(attr->revealContext()); + }); + break; + + case Text: + case ImagePath: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + markAsBadGeometry(attr->revealContext()); + }); + break; + + case Font: + case FontSize: + case TranslationO: + case TranslationA: + case MarginOppOut: + case MarginOppIn: + case MarginAdjIn: + case MarginAdjOut: + case PaddingOppOut: + case PaddingOppIn: + case PaddingAdjIn: + case PaddingAdjOut: + case SizeO: + case SizeA: + case AttributeKey::Orientation: + case WindowSizeStrategy: + case CellNames: + case Justify: + case AlignAdjacent: + case AlignOpposite: + case OverrideFillPrecedenceAdjacent: + case OverrideFillPrecedenceOpposite: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + markAsBadGeometry(attr->revealContext()); + }); + break; + + case Visibility: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + myDriver->invalidateVisibility(attr->revealContext()); + }); + break; + + case Borderless: + case Resizable: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + assert(false); + }); + break; + + case ElementColor: + case FontColor: + case BorderColor: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + markAsBadCanvas(attr->revealContext()); + }); + break; + + case Layout: + case Widget: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + setWidget(attr->revealContext()); + }); + break; + + case WindowState: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + //assert(false); //I dunno + }); + break; + + case InduceState: + attr->setChangeAcceptor([&, applyToStates](AnimatableAttribute* attr) + { + applyToStates(attr, [&](const StateKey key) + { + myStateManager.mutateIfObserved(key, true); + }); + }); + break; + + case BindState: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + //silent failure... Feck. + }); + break; + + case ToggleState: + attr->setChangeAcceptor([&, applyToStates](AnimatableAttribute* attr) + { + applyToStates(attr, [&](const StateKey key) + { + if(myStateManager.checkState(key)) + myStateManager.mutateIfObserved(key, false); + else + myStateManager.mutateIfObserved(key, true); + }); + }); + break; + + case UnsetState: + attr->setChangeAcceptor([&, applyToStates](AnimatableAttribute* attr) + { + applyToStates(attr, [&](const StateKey key) + { + myStateManager.mutateIfObserved(key, false); + }); + }); + break; + + case TriggerState: + attr->setChangeAcceptor([&, applyToStates](AnimatableAttribute* attr) + { + applyToStates(attr, [&](const StateKey key) + { + myStateManager.setTriggerIfObserved(key); + }); + }); + break; + + case RadioState: + attr->setChangeAcceptor([&, applyToStates](AnimatableAttribute* attr) + { + StateKey topKey; + uint64_t lastStamp = 0; + int count = 0; + + applyToStates(attr, [&](const StateKey key) + { + if(!myStateManager.checkState(key)) return; + + const uint64_t myStamp = myStateManager.getStamp(key); + + if(myStamp > lastStamp) + { + lastStamp = myStamp; + topKey = key; + } + + ++count; + }); + + if(count > 1) + { + applyToStates(attr, [&](const StateKey key) + { + if(key == topKey) return; + + myStateManager.mutateIfObserved(key, false); + }); + } + }); + break; + + case Triggers: + attr->setChangeAcceptor([&](AnimatableAttribute* attr) + { + DisplayItem* item = attr->revealContext(); + auto triggers = item->getAttr().getValueKeyList(AttributeKey::Triggers); + if(triggers.size()) triggerMap[item] = triggers; + }); + break; + } + +} + +}//IVD diff --git a/src/environment.h b/src/environment.h new file mode 100755 index 0000000..f35d0df --- /dev/null +++ b/src/environment.h @@ -0,0 +1,144 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef ENVIRONMENT_H +#define ENVIRONMENT_H + +#include +#include +#include + +#include "compiler.h" +#include "statemanager.h" +#include "displayitem.h" +#include "element.h" +#include "statekey.h" +#include "virtualstatekey.h" +#include "widget.h" + +#include "OpenImageIO/imagecache.h" + +namespace IVD +{ + +class AttributeSet; +class Driver; + + +class Environment +{ + std::unique_ptr managedDriver; + //Oh + Driver* myDriver; + + Compiler myComp; + + StateManager myStateManager; + + std::vector deferredVirtualStateKeys; + struct DeferredPositioning + { + DisplayItem* item; + IVD_Widget* parent; + }; + + std::vector deferredPositioning; + + std::map> instances; + + std::map widgetToDisplayItem; + + std::map> widgetOwnedDisplayItems; + + //This is for tracking items that need recomputing. + std::set itemsWithChangedAttributeSets; + + //These are for tracking attributes that have been recomputingted. + std::set attributeWantsAnimationTick; + std::set attributesThatHaveChanged; //No really we're changed let us out + std::set attributesThatMutateTheDrawTree; //Not convinced this is necessary... + + + std::map> triggerMap; + std::map elementLookupByPath; + std::map elementModelLookup; + + std::map> elementToDisplayItems; + + std::map widgetBlueprints; + std::map layoutBlueprints; + + + void initOthers(); + void processDeferredVirtualStates(); + void processDeferredPositioning(); + + DisplayItem* setupNewDisplayItem(Element* elem); + void destroyDisplayItem(DisplayItem* item); + void positionDisplayItemInDrawTree(DisplayItem* item, IVD_Widget *parentWidget); + void setWidget(DisplayItem* item); + + std::optional deduceTarget(DisplayItem* context, const ValueKeyPath key); + + double commonExternalAccessor(DisplayItem* context, + const ScopedValueKey key, + std::optional value); + + void markAsBadGeometry(DisplayItem* item); + void markAsBadCanvas(DisplayItem* item); + void updateHover(); + +public: + Environment(); + + //The big one + void run(); + + int loadFromIVDFile(const char* path); + + void registerWidgetBlueprints(const std::string name, const WidgetBlueprints blueprints) + { widgetBlueprints[name] = blueprints; } + + void registerLayoutBlueprints(const std::string name, const WidgetBlueprints blueprints) + { layoutBlueprints[name] = blueprints; } + + IVD_Widget* createWidget(const std::string name, IVD_Widget* parent); + IVD_Element* createIVDelementFromClass(const std::string className, IVD_Widget* parent); + + void destroyWidget(IVD_Widget* widget); + void destroyIVDelement(IVD_Widget* parent, IVD_Element* elem); + + //Doesn't work for layoutssssssss.... + DisplayItem* getUnderlyingDisplayItemForWidget(IVD_Widget* widget) + { return widgetToDisplayItem.at(widget); } + + Canvas* getCanvas(); + + Compiler* getCompiler() + { return &myComp; } + + Coords getMouseOffsetRelativeToWindow(); + + IVD_Widget* getChildWidgetForNamedCell(IVD_Widget* parent, const std::string name); + + const char* getCompilerErrors() + { return myComp.getErrorMessageDigest().c_str(); } + + double getInteger(DisplayItem* context, const ScopedValueKey key); + + void setInteger(DisplayItem* context, const ScopedValueKey key, const double val) + { commonExternalAccessor(context, key, val); } + + std::string getString(DisplayItem* context, const ScopedValueKey key); + + void markAsChangedAttributes(DisplayItem* item) + { itemsWithChangedAttributeSets.insert(item); } + + void setupEnvironmentCallbacksOnAttributeForKey(AnimatableAttribute* attr, const int key); + + StateKey generateStateKeyFromPrecursor(ScopedValueKey precursor, DisplayItem* baseContext); +}; + +}//IVD + + +#endif // ENVIRONMENT_H diff --git a/src/expression.cpp b/src/expression.cpp new file mode 100644 index 0000000..3509973 --- /dev/null +++ b/src/expression.cpp @@ -0,0 +1,161 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "expression.h" + +#include + +#include "environment.h" +#include "binaryexpressionprinter.h" + + +namespace IVD +{ + +std::string ExpressionNode::printoutThySelf() const +{ + std::string literal; //not to be confused with a standard string literal + + switch(operation) + { + case Keyword::ScalarType: + literal = std::to_string(val); + literal.push_back('i'); + break; + case Keyword::UnitPercent: + literal = std::to_string(val); + literal.push_back('%'); + break; + case Keyword::UnitStandard: + literal = std::to_string(val); + literal.push_back('u'); + break; + case IVD::Keyword::ScopedValueKey: + if(weakTerm) literal = "["; + literal += extVal.generatePrintout(); + if(weakTerm) literal += "]"; + break; + case IVD::Keyword::OperatorPlus: + literal = "(+)"; + break; + case IVD::Keyword::OperatorMinus: + literal = "(-)"; + break; + case IVD::Keyword::OperatorTimes: + literal = "(*)"; + break; + case IVD::Keyword::OperatorDivide: + literal = "(/)"; + break; + default: assert(false); + } + + return literal; +} + +double Expression::solveNode(const ExpressionNode* theNode, DisplayItem* theContext) const +{ + if(!theNode) return 0; + + const double left = solveNode(theNode->left.get(), theContext); + const double right = solveNode(theNode->right.get(), theContext); + + switch(theNode->operation) + { + case Keyword::UnitPercent: //Should probably do something with this... + case Keyword::UnitStandard: + case Keyword::ScalarType: return theNode->val; + case Keyword::ScopedValueKey: + assert(theContext); + return theContext->getEnv()->getInteger(theContext, theNode->extVal); + case Keyword::OperatorPlus: + return left + right; + case Keyword::OperatorMinus: + return left - right; + case Keyword::OperatorTimes: + return left * right; + case Keyword::OperatorDivide: + return left / right; + default: throw std::logic_error("Unsupported operator in solveNode. This can't happen."); + } +} + +double Expression::solveForUnknownNode(const ExpressionNode* theNode, + DisplayItem* theContext, + const double requiredResult) const +{ + if(theNode->weakTerm) + { + assert(theContext); + theContext->getEnv()->setInteger(theContext, theNode->extVal, requiredResult); + return requiredResult; + } + + if(!theNode->weakBranch) + { + std::cerr << "IVD Runtime: Attempting to solve for unknown on constant expression." << std::endl; + return 0; + } + + //The compiler should reject any code that leads to this condition, so + // no real error handling. + assert(!(theNode->left.get()->weakBranch && theNode->right.get()->weakBranch)); + + const bool leftUnknown = theNode->left.get()->weakBranch; + + const double knownValue = [&] + { + if(leftUnknown) + return solveNode(theNode->right.get(), theContext); + else + return solveNode(theNode->left.get(), theContext); + }(); + + ExpressionNode* weakNode = [&] + { + if(leftUnknown) + return theNode->left.get(); + else + return theNode->right.get(); + }(); + + //wheee + return solveForUnknownNode(weakNode, theContext, [&]() -> double + { + switch(theNode->operation) + { + case Keyword::OperatorPlus: return requiredResult - knownValue; + case Keyword::OperatorTimes: return requiredResult / knownValue; + + case Keyword::OperatorMinus: + if(leftUnknown) return knownValue + requiredResult; + else return knownValue - requiredResult; + + case Keyword::OperatorDivide: + if(leftUnknown) return knownValue * requiredResult; + else return knownValue / requiredResult; + default: throw std::logic_error("Unsupported operator in solveUnknownNode. This can't happen."); + } + }()); +} + +void Expression::applyToEachScopedValueKey(std::function fun) +{ + std::function applicator = [&](ExpressionNode* node) + { + if(node->operation == Keyword::ScopedValueKey) + node->extVal.apply(fun); + + if(node->left) applicator(node->left.get()); + if(node->right) applicator(node->right.get()); + }; + + applicator(&root); +} + +std::string Expression::generatePrintout() +{ + return generateExpressionPrintout(root); +} + + +}//IVD diff --git a/src/expression.h b/src/expression.h new file mode 100644 index 0000000..85d410b --- /dev/null +++ b/src/expression.h @@ -0,0 +1,123 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef EXPRESSION_H +#define EXPRESSION_H + +#include +#include + +#include +#include + +#include "statekey.h" //For ScopedValueKey +#include "keywords.h" +#include "codeposition.h" + +namespace IVD { class Element; class Compiler; } +void printOutAttributes(IVD::Compiler&, IVD::Element); + +namespace IVD +{ + +class DisplayItem; + +struct ExpressionNode +{ + //basic integer to work with values from keywords.h + //Accepts: Scalar, UnitPercent, UnitStandard, OperatorPlus, OperatorMinus, OperatorTimes, OperatorDivide + // and lest we forgets, ScopedValueKey + int operation; + + bool weakBranch; + bool weakTerm; //Only dependency can be weak; + + double val; + ScopedValueKey extVal; + + ExpressionNode(): weakBranch(false), weakTerm(false) {} + + ExpressionNode(const ExpressionNode& other) { this->operator=(other); } + + ExpressionNode& operator=(const ExpressionNode& other) + { + operation = other.operation; + weakBranch = other.weakBranch; + weakTerm = other.weakTerm; + val = other.val; + extVal = other.extVal; + + left.reset(); + right.reset(); + + if(other.left) left = std::make_unique(*other.left); + if(other.right) right = std::make_unique(*other.right); + + return *this; + } + + RUSTUTILS_DEFINE_COMP(ExpressionNode, operation, weakBranch, weakTerm, val, extVal) + + std::unique_ptr left; + std::unique_ptr right; + + void initLeft() { left = std::make_unique(); } + void initRight() { right = std::make_unique(); } + + std::string printoutThySelf() const; +}; + +class Expression +{ + ExpressionNode root; + + double solveNode(const ExpressionNode* theNode, DisplayItem* theContext) const; + double solveForUnknownNode(const ExpressionNode* theNode, + DisplayItem* theContext, + const double requiredResult) const; + +public: + Expression() {} + + Expression(const ExpressionNode& sourceRoot): + root(sourceRoot) + {} + + Expression(const ExpressionNode& sourceRoot, const CodePosition codepos): + root(sourceRoot), + definedAt(codepos) + {} + + //Rename this to unleash dragons. + CodePosition definedAt; + + void setRootNode(const ExpressionNode& sourceRoot) + { root = sourceRoot; } + + bool checkContainsWeak() const + { return root.weakTerm || root.weakBranch; } + + double solve(DisplayItem* theContext) const + { + return solveNode(&root, theContext); + } + + void solveForAndPropogateWeak(DisplayItem* theContext, const double requiredResult) const + { + solveForUnknownNode(&root, theContext, requiredResult); + } + + void applyToEachScopedValueKey(std::function fun); + + //But it doesn't compare context, eef... Context is really just a cache mechanism. + RUSTUTILS_DEFINE_COMP(Expression, root) + + std::string generatePrintout(); +}; + +typedef std::map DefineContainer; +typedef std::map SetContainer; + + +}//IVD + +#endif // EXPRESSION_H diff --git a/src/geometry.h b/src/geometry.h new file mode 100755 index 0000000..a1b386a --- /dev/null +++ b/src/geometry.h @@ -0,0 +1,3 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "user_include/cpp/IVD_geometry.h" diff --git a/src/geometryproposal.h b/src/geometryproposal.h new file mode 100644 index 0000000..610447f --- /dev/null +++ b/src/geometryproposal.h @@ -0,0 +1,4 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + + +#include "user_include/cpp/IVD_geometry_proposal.h" diff --git a/src/graph.cpp b/src/graph.cpp new file mode 100644 index 0000000..08be70a --- /dev/null +++ b/src/graph.cpp @@ -0,0 +1,130 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "graph.h" +#include "keywords.h" + +#include +#include + +namespace IVD +{ +namespace Animation +{ + +Graph::TwoSamplePoints Graph::getSamplePoints(double percent) const +{ + Sample leftSample = {0, 0}; + Sample rightSample = {1, 1}; + + for(auto it = mySamples.begin(); it != mySamples.end(); ++it) + { + auto sample = *it; + + if(sample.x == percent) return {sample, sample}; + + if(sample.x < percent) + { + if(it + 1 != mySamples.end()) continue; + + //Otherwise, we don't have a user-defined *right* weight that is as large + // as the percentage, which defaults to 1 @ 1, which is already set above. + //So we just use this one as the left weight + leftSample = leftSample; + break; + } + if(sample.x > percent) + { + rightSample = sample; + + if(it == mySamples.begin()) + { + //then, there is no defined weight small enough, in which case we + // define it as 0 @ 0, which is what leftWeight is already initialized to. + break; + } + + --it; + + leftSample = *it; + break; + } + } + + return {leftSample, rightSample}; +} + +double Graph::getLinearWeightForPercentage(double xpos) const +{ + const TwoSamplePoints points = getSamplePoints(xpos); + + if(points.left.y == points.right.y) + return points.left.y; + + //http://paulbourke.net/miscellaneous/interpolation/ + return points.left.y * (1 - xpos) + points.right.y * xpos; +} + +double Graph::getSmoothWeightForPercentage(double xpos) const +{ + const TwoSamplePoints points = getSamplePoints(xpos); + + if(points.left.y == points.right.y) + return points.left.y; + + //Used without understanding from: + //http://paulbourke.net/miscellaneous/interpolation/ + xpos = (1 - std::cos(xpos * M_PI)) / 2; + return points.left.y * (1 - xpos) + points.right.y * xpos; +} + +Graph::Graph(): interpolationMode(Keyword::Linear) {} + +int Graph::getInterpolatedScalarForPercentage(const int origin, + const int dest, + const double percentage) const +{ + const double destWeight = [&] + { + switch(interpolationMode) + { + case Keyword::Linear: return getLinearWeightForPercentage(percentage); + case Keyword::Smooth: return getSmoothWeightForPercentage(percentage); + default: assert(false); + } + }(); + + const double originWeight = 1 - destWeight; + return (origin * originWeight) + (dest * destWeight); +} + +std::string Graph::generatePrintout() +{ + std::stringstream ss; + ss << "Mode: "; + + if(interpolationMode == Keyword::Linear) ss << "Linear"; + else if(interpolationMode == Keyword::Smooth) ss << "Sinusoidal"; + else throw std::logic_error("Corrupt interpolation mode in Graph"); + + ss << ", samples: "; + + for(auto it = mySamples.begin(); it != mySamples.end(); ++it) + { + const Sample sample = *it; + ss << sample.x << " @ " << sample.y << (it + 1 != mySamples.end() ? ", " + : "."); + } + + return ss.str(); +} + +std::string Transition::generatePrintout() +{ + std::stringstream ss; + ss << "Time: " << miliseconds << "ms. " << graph.generatePrintout(); + return ss.str(); +} + +}//Animation +}//IVD + diff --git a/src/graph.h b/src/graph.h new file mode 100644 index 0000000..96f12e1 --- /dev/null +++ b/src/graph.h @@ -0,0 +1,82 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include +#include + +#include "rustutils/lexcompare.h" + +#include "codeposition.h" + +namespace IVD +{ + +namespace Animation +{ + +class Graph +{ +public: + struct Sample + { + double x; + double y; //Constantly double your weight with this one w e i r d t r i c k + + RUSTUTILS_DEFINE_COMP(Sample, x, y) + }; + +private: + + std::vector mySamples; + + struct TwoSamplePoints + { + Sample left; + Sample right; + }; + + int interpolationMode; + + TwoSamplePoints getSamplePoints(double percent) const; + + double getLinearWeightForPercentage(double percent) const; + double getSmoothWeightForPercentage(double percent) const; + +public: + Graph(); + Graph(const int interpolationMode): interpolationMode(interpolationMode) {} + + void addSample(const double weight, const double percent) + { mySamples.emplace_back(Sample{weight, percent}); } + + void setSamples(const std::vector samples) + { mySamples = samples; } + + int getInterpolatedScalarForPercentage(const int origin, + const int dest, + const double percentage) const; + + + + //Rename this to unleash dragons. + CodePosition definedAt; + + std::string generatePrintout(); + + RUSTUTILS_DEFINE_COMP(Graph, mySamples, interpolationMode, definedAt) +}; + +struct Transition +{ + int miliseconds; + Graph graph; //Girafe, what a gaffe... + + std::string generatePrintout(); + + RUSTUTILS_DEFINE_COMP(Transition, miliseconds, graph) +}; + + +}//Animation +}//IVD diff --git a/src/keywords.h b/src/keywords.h new file mode 100644 index 0000000..b115ead --- /dev/null +++ b/src/keywords.h @@ -0,0 +1,645 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef KEYWORDS_H +#define KEYWORDS_H + +#include "assert.h" +#include + +#include +#include + +#include +#include + +#include "attributebodytypes.h" + +namespace IVD +{ + + +namespace AttributeKey +{ + +enum +{ + FirstAttribute = 0, + + PositionWithin = FirstAttribute, + + TitleText, + Text, + + Font, + ImagePath, + + FontSize, + + TranslationO, + TranslationA, + + MarginOppOut, + MarginOppIn, + MarginAdjIn, + MarginAdjOut, + + PaddingOppOut, + PaddingOppIn, + PaddingAdjIn, + PaddingAdjOut, + + SizeO, + SizeA, + + //Properties + + Orientation, + + WindowState, + Visibility, + + AlignAdjacent, + AlignOpposite, + + OverrideFillPrecedenceAdjacent, + OverrideFillPrecedenceOpposite, + + Justify, + + WindowSizeStrategy, + + ImageSizeProperty, + + Borderless, + Resizable, + ModelOrder, + + + ElementColor, + FontColor, + BorderColor, + + + + Triggers, + InduceState, + BindState, + ToggleState, + UnsetState, + TriggerState, + RadioState, + + Layout, + Widget, + CellNames, + + AttributeCount, + + UnnaturalAttributesStart = AttributeCount, + + //These are unnatural (compound) Attributes. + Margins = UnnaturalAttributesStart, + Padding, + + LastUnnatturalAttribute = Padding, + LastAttribute = LastUnnatturalAttribute, + KeywordsStart = LastAttribute, +}; +} //AttributeKey + +namespace Keyword +{ +enum +{ + SchoolOperator = AttributeKey::KeywordsStart, + Dot, + Pound, + Arrow, + Colon, + Comma, + Semicolon, + OpenBracket, + CloseBracket, + OpenSquare, + CloseSquare, + OpenParen, + CloseParen, + + Pass, + Clear, + Delay, + EaseIn, + EaseOut, + Model, + State, + This, + Material, + + Start, + Min, + Max, + + Or, + And, + Xor, + Not, + + //Not exactly keywords... + ScalarType, + FloatType, + ScopedValueKey, + AttributeProperty, + UserToken, + UserString, + ColorLiteral, + + EqualSign, + UnitPercent, + UnitStandard, + UnitMilliseconds, + UnitSeconds, + + OperatorPlus, + OperatorMinus, + OperatorTimes, + OperatorDivide, + + Expression, + Graph, + ScalarKeyword, + Normalize, + + Linear, + Smooth, + + Declare, + Set, + + PropertiesStart +}; +}//Keywords + +namespace Property +{ + +enum +{ + PropertiesStart = Keyword::PropertiesStart, + AdjacentIsHorizontal = PropertiesStart, + AdjacentIsVertical, + + Greedy, + Shrinky, + + Maximized, + Minimized, + Fullscreen, + FullscreenTrue, + + TopDown, + BottomUp, + + Inner, + Center, + Outer, + + FontSans, + FontSansBold, + FontSansItalic, + FontSansBoldItalic, + + FontSerif, + FontSerifBold, + FontSerifItalic, + FontSerifBoldItalic, + + FontMono, + FontMonoBold, + FontMonoItalic, + FontMonoBoldItalic, + + OneToUnit, + Native, + Stretch, + BestFit, + + Enable, + Disable, + + PropertiesEnd, + + Custom = Keyword::UserToken, //Ah, cute. +}; + +}//Property + +namespace Spec +{ + +enum +{ + spec = Property::PropertiesEnd, + bleeding, +}; + +}//Spec + +inline std::map getStandardAttributes() +{ + using namespace AttributeKey; + //typedef AttributeBodyTypes B; + + //Start with T/F but found this more readable. + const bool X = true; + const bool O = false; + // single scoped value key + // state key list + // token list | + // token| | | + // string | | | positionWithin + // property | | | | unnatural + return {// expression| | | | |color| | + // | | | | | | | | | | + {Margins, {X, O, O, O, O, O, O, O, X, O}}, + {Padding, {X, O, O, O, O, O, O, O, X, O}}, + {FontSize, {X, O, O, O, O, O, O, O, O, O}}, + {TranslationO, {X, O, O, O, O, O, O, O, O, O}}, + {TranslationA, {X, O, O, O, O, O, O, O, O, O}}, + {MarginOppOut, {X, O, O, O, O, O, O, O, O, O}}, + {MarginOppIn, {X, O, O, O, O, O, O, O, O, O}}, + {MarginAdjIn, {X, O, O, O, O, O, O, O, O, O}}, + {MarginAdjOut, {X, O, O, O, O, O, O, O, O, O}}, + {PaddingOppOut, {X, O, O, O, O, O, O, O, O, O}}, + {PaddingOppIn, {X, O, O, O, O, O, O, O, O, O}}, + {PaddingAdjIn, {X, O, O, O, O, O, O, O, O, O}}, + {PaddingAdjOut, {X, O, O, O, O, O, O, O, O, O}}, + {SizeO, {X, O, O, O, O, O, O, O, O, O}}, + {SizeA, {X, O, O, O, O, O, O, O, O, O}}, + + {Orientation, {O, X, O, O, O, O, O, O, O, O}}, + {WindowState, {O, X, O, O, O, O, O, O, O, O}}, + {Visibility, {O, X, O, O, O, O, O, O, O, O}}, + {AlignAdjacent, {O, X, O, O, O, O, O, O, O, O}}, + {AlignOpposite, {O, X, O, O, O, O, O, O, O, O}}, + {OverrideFillPrecedenceAdjacent, {O, X, O, O, O, O, O, O, O, O}}, + {OverrideFillPrecedenceOpposite, {O, X, O, O, O, O, O, O, O, O}}, + {Justify, {O, X, O, O, O, O, O, O, O, O}}, + {WindowSizeStrategy, {O, X, O, O, O, O, O, O, O, O}}, + {ImageSizeProperty, {O, X, O, O, O, O, O, O, O, O}}, + {Borderless, {O, X, O, O, O, O, O, O, O, O}}, + {Resizable, {O, X, O, O, O, O, O, O, O, O}}, + {ModelOrder, {O, X, O, O, O, O, O, O, O, O}}, + {Font, {O, X, X, O, O, O, O, O, O, O}}, + + {TitleText, {O, O, X, O, O, O, X, O, O, O}}, + {Text, {O, O, X, O, O, O, X, O, O, O}}, + {ImagePath, {O, O, X, O, O, O, O, O, O, O}}, + + {Layout, {O, O, O, X, O, O, O, O, O, O}}, + {Widget, {O, O, O, X, O, O, O, O, O, O}}, + + {CellNames, {O, O, O, O, X, O, O, O, O, O}}, + + {Triggers, {O, O, O, O, O, X, O, O, O, O}}, + {InduceState, {O, O, O, O, O, X, O, O, O, O}}, + {BindState, {O, O, O, O, O, X, O, O, O, O}}, + {ToggleState, {O, O, O, O, O, X, O, O, O, O}}, + {UnsetState, {O, O, O, O, O, X, O, O, O, O}}, + {TriggerState, {O, O, O, O, O, X, O, O, O, O}}, + {RadioState, {O, O, O, O, O, X, O, O, O, O}}, + + {ElementColor, {O, O, O, O, O, O, O, X, O, O}}, + {FontColor, {O, O, O, O, O, O, O, X, O, O}}, + {BorderColor, {O, O, O, O, O, O, O, X, O, O}}, + + + + {PositionWithin, {O, O, O, O, O, O, O, O, O, X}}, + }; +} + + +//Vector because the order is important +inline std::map> getNaturalKeysToUnnaturalKeyMap() +{ + using namespace AttributeKey; + return { + {Margins, {MarginOppOut, MarginOppIn, MarginAdjIn, MarginAdjOut}}, + {Padding, {PaddingOppOut, PaddingOppIn, PaddingAdjIn, PaddingAdjOut}}, + }; +} + +inline std::map> getAttributeKeyToValidPropertyList() +{ + const std::initializer_list fillprecedenceSet = {Property::Greedy, Property::Shrinky}; + const std::initializer_list booleanSet = {Property::Enable, Property::Disable}; + const std::initializer_list alignSet = {Property::Inner, Property::Center, Property::Outer}; + + return { + {AttributeKey::Font, + {Property::FontSans, + Property::FontSansBold, + Property::FontSansItalic, + Property::FontSansBoldItalic, + Property::FontSerif, + Property::FontSerifBold, + Property::FontSerifItalic, + Property::FontSerifBoldItalic, + Property::FontMono, + Property::FontMonoBold, + Property::FontMonoItalic, + Property::FontMonoBoldItalic}}, + {AttributeKey::Orientation, + {Property::AdjacentIsHorizontal, + Property::AdjacentIsVertical}}, + {AttributeKey::OverrideFillPrecedenceAdjacent, + fillprecedenceSet}, + {AttributeKey::OverrideFillPrecedenceOpposite, + fillprecedenceSet}, + {AttributeKey::WindowState, + {Property::Maximized, + Property::Minimized, + Property::Fullscreen, + Property::FullscreenTrue}}, + {AttributeKey::Visibility, + {Property::Enable, + Property::Disable}}, + {AttributeKey::AlignAdjacent, + alignSet}, + {AttributeKey::AlignOpposite, + alignSet}, + {AttributeKey::Justify, + alignSet}, + {AttributeKey::WindowSizeStrategy, + {Property::TopDown, + Property::BottomUp}}, + {AttributeKey::ImageSizeProperty, + {Property::OneToUnit, + Property::Native, + Property::Stretch, + Property::BestFit}}, + {AttributeKey::Borderless, + booleanSet}, + {AttributeKey::Resizable, + booleanSet}, + {AttributeKey::ModelOrder, + booleanSet}, + }; +} + +inline std::map getTokenToSymbolMap() +{ + return {{"@", Keyword::SchoolOperator}, + {".", Keyword::Dot}, + {"#", Keyword::Pound}, + {"->", Keyword::Arrow}, + {":", Keyword::Colon}, + {",", Keyword::Comma}, + {";", Keyword::Semicolon}, + {"{", Keyword::OpenBracket}, + {"}", Keyword::CloseBracket}, + {"[", Keyword::OpenSquare}, + {"]", Keyword::CloseSquare}, + {"(", Keyword::OpenParen}, + {")", Keyword::CloseParen}, + + {"=", Keyword::EqualSign}, + {"%", Keyword::UnitPercent}, + {"u", Keyword::UnitStandard}, + {"ms", Keyword::UnitMilliseconds}, + {"milliseconds", Keyword::UnitMilliseconds}, + {"sec", Keyword::UnitSeconds}, + {"seconds", Keyword::UnitSeconds}, + + {"+", Keyword::OperatorPlus}, + {"-", Keyword::OperatorMinus}, + {"*", Keyword::OperatorTimes}, + {"/", Keyword::OperatorDivide}, + + {"expression", Keyword::Expression}, + {"graph", Keyword::Graph}, + {"scalar", Keyword::ScalarKeyword}, + + {"linear", Keyword::Linear}, + {"smooth", Keyword::Smooth}, + + {"declare", Keyword::Declare}, + {"set", Keyword::Set}, + + {"normalize", Keyword::Normalize}, + + {"pass", Keyword::Pass}, + {"clear", Keyword::Clear}, + {"delay", Keyword::Delay}, + {"ease-in", Keyword::EaseIn}, + {"ease-out", Keyword::EaseOut}, + {"model", Keyword::Model}, + {"state", Keyword::State}, + {"this", Keyword::This}, + {"material", Keyword::Material}, + + {"start", Keyword::Start}, + {"min", Keyword::Min}, + {"max", Keyword::Max}, + + + //Bitwise gamgee + {"or", Keyword::Or}, + {"|", Keyword::Or}, + {"and", Keyword::And}, + {"&", Keyword::And}, + {"xor", Keyword::Xor}, + {"!=", Keyword::Xor}, + {"not", Keyword::Not}, + {"!", Keyword::Not}, + + + {"position-within", AttributeKey::PositionWithin}, + + {"title-text", AttributeKey::TitleText}, + {"text", AttributeKey::Text}, + + {"font", AttributeKey::Font}, + + {"image-path", AttributeKey::ImagePath}, + + {"font-size", AttributeKey::FontSize}, + + {"orientation", AttributeKey::Orientation}, + + {"trans-o", AttributeKey::TranslationO}, + {"trans-a", AttributeKey::TranslationA}, + {"trans-y", AttributeKey::TranslationO}, + {"trans-x", AttributeKey::TranslationA}, + + {"size-o", AttributeKey::SizeO}, + {"size-a", AttributeKey::SizeA}, + {"size-y", AttributeKey::SizeO}, + {"size-x", AttributeKey::SizeA}, + {"height", AttributeKey::SizeO}, + {"width", AttributeKey::SizeA}, + + {"margin-o-out", AttributeKey::MarginOppOut}, + {"margin-o-in", AttributeKey::MarginOppIn}, + {"margin-a-in", AttributeKey::MarginAdjIn}, + {"margin-a-out", AttributeKey::MarginAdjOut}, + + {"margin-bottom", AttributeKey::MarginOppOut}, + {"margin-top", AttributeKey::MarginOppIn}, + {"margin-left", AttributeKey::MarginAdjIn}, + {"margin-right", AttributeKey::MarginAdjOut}, + + {"padding-o-out", AttributeKey::PaddingOppOut}, + {"padding-o-in", AttributeKey::PaddingOppIn}, + {"padding-a-in", AttributeKey::PaddingAdjIn}, + {"padding-a-out", AttributeKey::PaddingAdjOut}, + + {"padding-bottom", AttributeKey::PaddingOppOut}, + {"padding-top", AttributeKey::PaddingOppIn}, + {"padding-left", AttributeKey::PaddingAdjIn}, + {"padding-right", AttributeKey::PaddingAdjOut}, + + {"window-state", AttributeKey::WindowState}, + {"visibility", AttributeKey::Visibility}, + + {"align-a", AttributeKey::AlignAdjacent}, + {"align-o", AttributeKey::AlignOpposite}, + + {"align-x", AttributeKey::AlignAdjacent}, + {"align-y", AttributeKey::AlignOpposite}, + + {"fill-precedence-adjacent", AttributeKey::OverrideFillPrecedenceAdjacent}, + {"fill-precedence-opposite", AttributeKey::OverrideFillPrecedenceOpposite}, + + {"fill-precedence-a", AttributeKey::OverrideFillPrecedenceAdjacent}, + {"fill-precedence-o", AttributeKey::OverrideFillPrecedenceOpposite}, + + {"fill-precedence-x", AttributeKey::OverrideFillPrecedenceAdjacent}, + {"fill-precedence-y", AttributeKey::OverrideFillPrecedenceOpposite}, + + {"justify", AttributeKey::Justify}, + + {"window-size-strategy", AttributeKey::WindowSizeStrategy}, + + {"borderless", AttributeKey::Borderless}, + {"resizable", AttributeKey::Resizable}, + {"model-order", AttributeKey::ModelOrder}, + + {"color", AttributeKey::ElementColor}, + {"font-color", AttributeKey::FontColor}, + {"border-color", AttributeKey::BorderColor}, + + {"induce-state", AttributeKey::InduceState}, + {"bind-state", AttributeKey::BindState}, + {"toggle-state", AttributeKey::ToggleState}, + {"unset-state", AttributeKey::UnsetState}, + {"trigger-state", AttributeKey::TriggerState}, + {"radio-state", AttributeKey::RadioState}, + + {"trigger", AttributeKey::Triggers}, + + {"layout", AttributeKey::Layout}, + {"widget", AttributeKey::Widget}, + {"cell-names", AttributeKey::CellNames}, + + //Unnaturals + {"margin", AttributeKey::Margins}, + {"padding", AttributeKey::Padding}, + + //Property values + + {"adjacent-is-horizontal", Property::AdjacentIsHorizontal}, + {"adjacent-is-vertical", Property::AdjacentIsVertical}, + + {"greedy", Property::Greedy}, + {"shrinky", Property::Shrinky}, + + {"maximize", Property::Maximized}, + {"minimize", Property::Minimized}, + {"fullscreen", Property::Fullscreen}, + {"fullscreen-true", Property::FullscreenTrue}, + + {"top-down", Property::TopDown}, + {"bottom-up", Property::BottomUp}, + + {"align-inner", Property::Inner}, + {"align-center", Property::Center}, + {"align-outer", Property::Outer}, + {"align-left", Property::Inner}, + {"align-right", Property::Outer}, + {"align-top", Property::Inner}, + {"align-bottom", Property::Outer}, + + {"sans", Property::FontSans}, + {"sans-bold", Property::FontSansBold}, + {"sans-italic", Property::FontSansItalic}, + {"sans-bold-italic", Property::FontSansBoldItalic}, + {"sans-italic-bold", Property::FontSansBoldItalic}, + + {"serif", Property::FontSerif}, + {"serif-bold", Property::FontSerifBold}, + {"serif-italic", Property::FontSerifItalic}, + {"serif-bold-italic", Property::FontSerifBoldItalic}, + {"serif-italic-bold", Property::FontSerifBoldItalic}, + + {"mono", Property::FontMono}, + {"mono-bold", Property::FontMonoBold}, + {"mono-italic", Property::FontMonoItalic}, + {"mono-bold-italic", Property::FontMonoBoldItalic}, + {"mono-italic-bold", Property::FontMonoBoldItalic}, + + {"one-to-unit", Property::OneToUnit}, + {"native", Property::Native}, + {"stretch", Property::Stretch}, + {"best-fit", Property::BestFit}, + + {"enable", Property::Enable}, + {"disable", Property::Disable}, + + + + + + + {"spec", Spec::spec}, + {"bleeding", Spec::bleeding}, + + }; +} + +inline std::set getDelimitingSymbolSet() +{ + return {Keyword::SchoolOperator, + Keyword::Dot, + Keyword::Pound, + Keyword::Arrow, + Keyword::Colon, + Keyword::Comma, + Keyword::Semicolon, + Keyword::OpenBracket, + Keyword::CloseBracket, + Keyword::OpenSquare, + Keyword::CloseSquare, + Keyword::OpenParen, + Keyword::CloseParen, + Keyword::Not, + + }; +} + +//------------------------------------Helpers below, all syntax defined above +inline std::map getSymbolToTokenMap() +{ + std::map result; + auto aye = getTokenToSymbolMap(); + + //This will overwrite aliases :/ + for(auto pair : aye) + result[pair.second] = pair.first; + + return result; +} + +}//IVD + +#endif // KEYWORDS_H diff --git a/src/referenceattribute.cpp b/src/referenceattribute.cpp new file mode 100644 index 0000000..78eed32 --- /dev/null +++ b/src/referenceattribute.cpp @@ -0,0 +1,59 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "referenceattribute.h" + +#include "rustutils/routine.h" + +namespace IVD +{ + +void ReferenceAttribute::derive(const ReferenceAttribute& other) +{ + //This seems backwards for the runtime... Should just copy-in the cleared + // attribute if we're not in mode-derive? + // Shouldn't this be read ther otherway around? TODO + if(clear) return; + + if(!other.active) return; + + active = true; + + auto mergeHelper = [](auto& mine, const auto& other) + { + if(!mine) mine = other; + }; + + mergeHelper(property, other.property); + + mergeHelper(starting, other.starting); + mergeHelper(min, other.min); + mergeHelper(max, other.max); + mergeHelper(expr, other.expr); + + mergeHelper(color, other.color); + + mergeHelper(literal, other.literal); + mergeHelper(singleKey, other.singleKey); + + mergeHelper(delay, other.delay); + mergeHelper(ease, other.ease); + + RustUtils::Routine::appendContainer(keys, other.keys); + RustUtils::Routine::appendContainer(literalList, other.literalList);} + +void ReferenceAttribute::applyToEachScopedValueKey(std::function fun) +{ + auto guard = [&](std::optional& optExpr) + { + if(optExpr) optExpr->applyToEachScopedValueKey(fun); + }; + + guard(starting); + guard(min); + guard(max); + guard(expr); + + for(ScopedValueKey& key : keys) fun(key); + if(singleKey) fun(*singleKey); +} +}//IVD diff --git a/src/referenceattribute.h b/src/referenceattribute.h new file mode 100644 index 0000000..31a1602 --- /dev/null +++ b/src/referenceattribute.h @@ -0,0 +1,41 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include "expression.h" +#include "color.h" +#include "graph.h" + +namespace IVD +{ + +struct ReferenceAttribute +{ + bool active = false; + bool clear = false; + bool stateModifierAttr = false; + + std::optional property; + + std::optional starting; + std::optional min; + std::optional max; + + std::optional expr; + + std::optional color; + + std::optional literal; + std::vector literalList; //Don't be such a literalist GODDDDDDDD + + std::optional singleKey; + std::vector keys; + + std::optional delay; + std::optional ease; + + void derive(const ReferenceAttribute& other); + void applyToEachScopedValueKey(std::function fun); +}; + +}//IVD diff --git a/src/referenceattributeset.cpp b/src/referenceattributeset.cpp new file mode 100644 index 0000000..5c4458e --- /dev/null +++ b/src/referenceattributeset.cpp @@ -0,0 +1,42 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + + +#include "referenceattributeset.h" + +namespace IVD +{ + +void ReferenceAttributeSet::mergeModifiers(const ReferenceAttributeSet& other) +{ + //declares are always on the default state, but since this method + // is used to merge classes as well as states, we just merge declares + // here, and rely on the compiler to prevent declares from being added + // to attribute sets that refer to a different state. + + for(auto pair : other.declareModifiers) + declareModifiers[pair.first] = pair.second; + + for(auto pair : other.setModifiers) + setModifiers[pair.first] = pair.second; +} + +void ReferenceAttributeSet::deriveFrom(const ReferenceAttributeSet& other) +{ + mergeModifiers(other); + + for(int key = 0; key != AttributeKey::AttributeCount; ++key) + attr[key].derive(other.attr[key]); +} + +void ReferenceAttributeSet::applyToEachScopedValueKey(std::function fun) +{ + for(auto& it : declareModifiers) + it.second.applyToEachScopedValueKey(fun); + for(auto& it : setModifiers) + it.second.applyToEachScopedValueKey(fun); + + for(int i = 0; i != AttributeKey::AttributeCount; ++i) + attr[i].applyToEachScopedValueKey(fun); +} + +}//IVD diff --git a/src/referenceattributeset.h b/src/referenceattributeset.h new file mode 100644 index 0000000..ada95f6 --- /dev/null +++ b/src/referenceattributeset.h @@ -0,0 +1,49 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include +#include "keywords.h" +#include "referenceattribute.h" + +namespace IVD +{ + +struct ReferenceAttributeSet +{ + typedef std::map DeclareModifierMap; + typedef std::map SetModifierMap; + + std::vector attr; + + //Set can modify but not observe. + //Declare can observe, and provide a filter for modification, + DeclareModifierMap declareModifiers; + SetModifierMap setModifiers; + + ReferenceAttributeSet(const int attrCount): + attr(attrCount) + {} + + int size() const + { return attr.size(); } + + ReferenceAttribute getAttribute(const int key) const + { return attr[key]; } + + void insertAttribute(ReferenceAttribute theAttribute, const int key) + { attr[key] = theAttribute; } + + void insertDeclareModifier(ValueKey target, Expression expr) + { declareModifiers[target] = expr; } + + void insertSetModifier(ScopedValueKey target, Expression expr) + { setModifiers[target] = expr; } + + void mergeModifiers(const ReferenceAttributeSet& other); + + void deriveFrom(const ReferenceAttributeSet& other); + void applyToEachScopedValueKey(std::function fun); +}; + +}//IVD diff --git a/src/runtimeattribute.cpp b/src/runtimeattribute.cpp new file mode 100644 index 0000000..07a7f17 --- /dev/null +++ b/src/runtimeattribute.cpp @@ -0,0 +1,268 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "runtimeattribute.h" + +#include "rustutils/routine.h" + +#include "runtimeattributeset.h" + +#include + +namespace IVD +{ + +std::optional RuntimeAttribute::getValue(DisplayItem* theContext) const +{ + //For simplicity's sake, min/max are used to round off observed values, + // but are not calculated in back-propogation, and thus there is no + // back and forth. An attribute simply takes in a suggested value, + // calculates it, and when you need to actually observe the value + // (which is done in a seperate function from from set), the bounds + // are checked, and rounded, and that should be enough. If there are + // min/max constraints on both attributes, it gets complicated real quick, + // and I haven't found a good use case for that. min/max is good for + // making sure a scalar is within range, but I see no reason why + // the two values must agree if they both have constraints. + //tl;dl local attribute min/max takes precedence over everything else. + + std::optional value; + + if(!expr) return value; + + value = expr->solve(theContext); + + if(min) + { + const double computedMin = min->solve(theContext); + if(computedMin > value) value = computedMin; + } + + if(max) + { + const double computedMax = max->solve(theContext); + if(computedMax < value) value = computedMax; + } + + return value; +} + +void RuntimeAttribute::setValue(const double proposed, DisplayItem* theContext) +{ + starting = nullptr; + + if(expr) expr->solveForAndPropogateWeak(theContext, proposed); +} + +template +static void mergeHelper(T& mine, const TO& other) +{ + if(other) mine = &other; +} + +template +static void mergeHelper(T& mine, const std::optional& other) +{ + if(other) mine = &*other; +} + +template +static void mergeHelper(T& mine, const std::vector& other) +{ + if(other.size()) mine = other; +} + +void RuntimeAttribute::merge(const ReferenceAttribute& other) +{ + mergeHelper(property, other.property); + mergeHelper(starting, other.starting); + mergeHelper(min, other.min); + mergeHelper(max, other.max); + mergeHelper(expr, other.expr); + mergeHelper(color, other.color); + mergeHelper(literal, other.literal); + mergeHelper(singleKey, other.singleKey); + + RustUtils::Routine::appendContainer(keys, other.keys); + RustUtils::Routine::appendContainer(literalList, other.literalList); +} + +void RuntimeAttribute::reset() +{ + clear = nullptr; + property = nullptr; + starting = min = max = expr = nullptr; + color = nullptr; + literal = nullptr; + literalList.clear(); + singleKey = nullptr; + keys.clear(); +} + +const RuntimeAttribute& AnimatableAttribute::getCorrectRTA() const +{ + return lastRatio == 1 ? currentRTA + : previousRTA; +} + +void AnimatableAttribute::animationTick() +{ + double changeRatio = 0; + + if(!animationStart) + { + animationStart = std::chrono::steady_clock::now(); + lastRatio = 0; //todo + } + else + { + //Guaranteed to only be ease or delay. + const auto maxDuration = std::chrono::milliseconds(ease ? ease->miliseconds + : *delay); + + const auto startTimePoint = (*animationStart); + const auto elapsedTime = std::chrono::duration_cast(std::chrono::steady_clock::now() - startTimePoint); + + + if(elapsedTime >= maxDuration) + { + changeRatio = 1; + } + else if(ease) //Delay kind of takes care of itself~ + { + const double e = elapsedTime.count(); + const double m = maxDuration.count(); + + + + changeRatio = e / m; + } + } + + //This block is for Reprodyne stuff + { + //Scope to displayitem itself because our pointer should be able to change. + std::string key; + key = std::to_string(myAttributeKey); + key += "-:-animation-ratio"; + + //Needs to intercept time or we can't validate the correct calculation of the + // change ratio....... TODO + changeRatio = reprodyne_intercept_double(revealContext(), key.c_str(), changeRatio); + } + + if(lastRatio != changeRatio) + { + lastRatio = changeRatio; + signalChangedAttribute(this); + } + + if(lastRatio == 1) quitAnimation(); + +} + +void AnimatableAttribute::beginAttributeRecompute() +{ + //We store this not only for animation, but for checking if anything + // changed in commitAttributeRecompute() + checkpointRTA = currentRTA; + + ease = nullptr; + delay = nullptr; + active = false; + + currentRTA.reset(); +} + +void AnimatableAttribute::merge(const ReferenceAttribute& ref) +{ + if(currentRTA.checkClear() || !ref.active) return; + + active = true; + + if(ref.ease) ease = &*ref.ease; + if(ref.delay) delay = &*ref.delay; + + currentRTA.merge(ref); +} + +void AnimatableAttribute::commitAttributeRecompute() +{ + //This doesn't check our full configuration (clear........) + if(checkpointRTA == currentRTA) return; + + previousRTA = checkpointRTA; + + if(ease || delay) + { + lastRatio = 0; + requestAnimationTicker(this); + } + else + { + lastRatio = 1; + cancelAnimationTicker(this); //Why not call "quitAnimation"? TODO + } + + signalChange(); +} + + +std::optional AnimatableAttribute::getValue() const +{ + std::optional result; + + if(lastRatio != 1) + { + auto optionalOrigin = previousRTA.getValue(theContext); + auto optionalDest = currentRTA.getValue(theContext); + + if(!optionalOrigin || !optionalDest) return result; //wtf condition + + return ease->graph.getInterpolatedScalarForPercentage(*optionalOrigin, + *optionalDest, + lastRatio); + } + else return currentRTA.getValue(theContext); +} + + +std::optional AnimatableAttribute::getProperty() const +{ + const int* i = getCorrectRTA().property; + return i ? *i + : std::optional(); +} + +std::vector AnimatableAttribute::getLiteralList() const +{ + return getCorrectRTA().literalList; +} + +std::optional AnimatableAttribute::getSingleValueKey() const +{ + const ScopedValueKey* i = getCorrectRTA().singleKey; + return i ? *i + : std::optional(); +} + +std::vector AnimatableAttribute::getValueKeyList() const +{ + return getCorrectRTA().keys; +} + +std::optional AnimatableAttribute::getUserToken() const +{ + const std::string* i = getCorrectRTA().literal; + return i ? *i + : std::optional(); +} + +std::optional AnimatableAttribute::getColor() const +{ + const Color* i = getCorrectRTA().color; + return i ? *i + : std::optional(); +} + + +} //IVD diff --git a/src/runtimeattribute.h b/src/runtimeattribute.h new file mode 100644 index 0000000..4b945a9 --- /dev/null +++ b/src/runtimeattribute.h @@ -0,0 +1,187 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef ATTRIBUTE_H +#define ATTRIBUTE_H + +#include +#include + +#include "rustutils/lexcompare.h" +#include "rustutils/easyuniquepointer.h" + +#include "color.h" +#include "expression.h" +#include "graph.h" + +#include "referenceattribute.h" + + +namespace IVD +{ + + +class AnimatableAttribute; + + +class RuntimeAttribute //1224 bytes before 632 after~ NOW 248... 96... 128 Fack +{ + //Doesn't really need to be a friend it can just be all public because AnimatableAttribute + // is all that uses it... Or just make it internal, rename to data, save "runtimeAttribute" + // as the public class name..... + friend class AnimatableAttribute; + int myAttributeKey = -1; //TODO: make constant + + //No "active" because if this exists, it's active + + const bool* clear = nullptr; + const int* property = nullptr; + const Expression* starting = nullptr; + const Expression* min = nullptr; + const Expression* max = nullptr; + const Expression* expr = nullptr; + const Color* color = nullptr; + const std::string* literal = nullptr; + std::vector literalList; //Don't be such a literalist GODDDDDDDD + const ScopedValueKey* singleKey = nullptr; + std::vector keys; + +public: + + void initializeTransitionSystem(const int key) + { myAttributeKey = key; } + + void reset(); + + + std::optional getValue(DisplayItem* theContext) const; + void setValue(const double proposed, DisplayItem* theContext); + + void merge(const ReferenceAttribute& other); + + void applyToEachScopedValueKey(std::function fun); + + auto getScopedValueKeys() + { return keys; } + + bool checkClear() + { return clear; } + + RUSTUTILS_DEFINE_COMP(RuntimeAttribute, + clear, + property, + starting, min, max, expr, + color, + literal, + literalList, + singleKey, + keys) +}; + +class AnimatableAttribute //440 byte struct replacing a 1224 byte struct, noice +{ + int myAttributeKey = -1; + DisplayItem* theContext = nullptr; + + bool active = false; + + RuntimeAttribute previousRTA; //Lol RTA + RuntimeAttribute currentRTA; + + + //We need a third copy in case the values are the same as the current and as such + // the recompute is redundant. (TODO: Should recomputes just not do this, period?) + RuntimeAttribute checkpointRTA; + + //These point to the (current) reference attribute + const int* delay = nullptr; //Yeah it's silly indirection for an integer but it's consistent. + const Animation::Transition* ease = nullptr; + + std::optional> animationStart; + //lastRatio == 1 means animation is finished. + double lastRatio = 1; + + std::function signalChangedAttribute; + std::function changeAcceptor; + std::function requestAnimationTicker; + std::function cancelAnimationTicker; + + const RuntimeAttribute& getCorrectRTA() const; + +public: + + void init(DisplayItem* thethecontext, const int key) + { + theContext = thethecontext; + myAttributeKey = key; + } + + bool checkActive() + { return active; } + + bool thisIsAhackButCheckIfThereIsDelay() + { return delay; } + + DisplayItem* revealContext() + { return theContext; } + + void setSignalChangedAttribute(std::function fun) + { signalChangedAttribute = fun; } + void setChangeAcceptor(std::function fun) + { changeAcceptor = fun; } + void setAnimationTickRequester(std::function fun) + { requestAnimationTicker = fun; } //I mean, it *is* a lot of fun! + void setCancelAnimationTicker(std::function fun) + { cancelAnimationTicker = fun; } + + void animationTick(); + + void quitAnimation() + { + cancelAnimationTicker(this); + animationStart.reset(); + } + + void executeChangeAcceptor() + { + assert(changeAcceptor); //Because assert gives line numbers and a bad function call exception does not. + changeAcceptor(this); + } + + void signalChange() + { + assert(signalChangedAttribute); + + signalChangedAttribute(this); + } + + RuntimeAttribute& getCurrent() + { return currentRTA; } + + void beginAttributeRecompute(); + void merge(const ReferenceAttribute& ref); + void commitAttributeRecompute(); + + + //data interface + void setValue(const double proposed) + { currentRTA.setValue(proposed, theContext); } + + std::optional getValue() const; + bool checkExprIsConst() const + { + auto expr = currentRTA.expr; + return expr ? !expr->checkContainsWeak() + : true; + } + std::optional getProperty() const; + std::vector getLiteralList() const; + std::optional getSingleValueKey() const; + std::vector getValueKeyList() const; + std::optional getUserToken() const; + std::optional getColor() const; +}; + + +}//IVD + +#endif // ATTRIBUTE_H diff --git a/src/runtimeattributeset.cpp b/src/runtimeattributeset.cpp new file mode 100644 index 0000000..d0ee705 --- /dev/null +++ b/src/runtimeattributeset.cpp @@ -0,0 +1,114 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include + +#include "runtimeattributeset.h" +#include "environment.h" + +namespace IVD +{ + + +RuntimeAttributeSet::RuntimeAttributeSet(DisplayItem* context, const ReferenceAttributeSet& initializerSet): + myContext(context), + attrs(initializerSet.size(), AnimatableAttribute()) +{ + //We only want the initializer set to initialize the state changing attribute set. + //Otherwise we just need the attr size + assert(myContext); + for(int key = 0; key != initializerSet.size(); ++key) + { + attrs.at(key).init(context, key); + context->getEnv()->setupEnvironmentCallbacksOnAttributeForKey(&attrs.at(key), key); + + if(initializerSet.attr[key].stateModifierAttr) + stateChangingAttributes.push_back(&attrs.at(key)); + } +} + +void RuntimeAttributeSet::applyToEachAttribute(std::function fun) +{ + for(int key = 0; key != AttributeKey::AttributeCount; ++key) + fun(attrs[key]); +} + +void RuntimeAttributeSet::beginAttributeSetRecompute() +{ + declareModifiers = nullptr; + setModifiers.clear(); + + applyToEachAttribute([&](AnimatableAttribute& a) + { a.beginAttributeRecompute(); }); +} + +void RuntimeAttributeSet::mergeIn(const ReferenceAttributeSet& other) +{ + //We just pretend that the data is clean and that only the first + // reference set has declare modifiers... + if(!declareModifiers) declareModifiers = &other.declareModifiers; + + setModifiers.push_back(&other.setModifiers); + + for(int key = 0; key != AttributeKey::AttributeCount; ++key) + attrs[key].merge(other.attr[key]); +} + +void RuntimeAttributeSet::commitAttributeSetRecompute() +{ + applyToEachAttribute([&](AnimatableAttribute& a) + { a.commitAttributeRecompute(); }); +} + +void RuntimeAttributeSet::executeStateChangers() +{ + //We only update state modifiers every time the attribute set is updated. Not triggers. + //TODO unclear code... + for(auto attr : stateChangingAttributes) + { + //Not considered for quick, logical things + //TODOOOOOOOO + if(attr->thisIsAhackButCheckIfThereIsDelay()) continue; + + attr->executeChangeAcceptor(); + } +} + +void RuntimeAttributeSet::fireSets() +{ + std::unordered_set firedExpressions; + + //This is still O(N), the inner loop is just a sub-range don't lose your shit. + for(auto& innerSet : setModifiers) + { + for(auto& pair : *innerSet) + { + const Expression* expr = &pair.second; + + if(firedExpressions.count(expr)) continue; + + myContext->getEnv()->setInteger(myContext, pair.first, expr->solve(myContext)); + + firedExpressions.insert(expr); + } + } +} + +std::optional RuntimeAttributeSet::getDeclaredInt(ValueKey key) +{ + if(!declareModifiers->count(key)) return std::optional(); + return declareModifiers->at(key).solve(myContext); +} + +void RuntimeAttributeSet::setDeclaredInt(const ValueKey key, const double proposed) +{ + assert(declareModifiers->count(key)); + + const Expression& expr = declareModifiers->at(key); + + assert(expr.checkContainsWeak()); + + expr.solveForAndPropogateWeak(myContext, proposed); +} + + +}//IVD diff --git a/src/runtimeattributeset.h b/src/runtimeattributeset.h new file mode 100644 index 0000000..eecc8bf --- /dev/null +++ b/src/runtimeattributeset.h @@ -0,0 +1,93 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef ATTRIBUTESET_H +#define ATTRIBUTESET_H + +#include +#include +#include +#include +#include +#include + +#include "rustutils/routine.h" + +#include "referenceattributeset.h" +#include "runtimeattribute.h" +#include "statekey.h" +#include "keywords.h" + +namespace IVD +{ + +class DisplayItem; + +class RuntimeAttributeSet +{ + friend class Compiler; + friend void ::printOutAttributes(IVD::Compiler&, IVD::Element); + + DisplayItem* myContext; + + std::vector attrs; + //These are pointers into attrs, which is initialized to it's + // final size so it should never invalidate. + std::vector stateChangingAttributes; + + const ReferenceAttributeSet::DeclareModifierMap* declareModifiers; + std::vector setModifiers; + + void applyToEachAttribute(std::function fun); + +public: + RuntimeAttributeSet(): myContext(nullptr) {} + RuntimeAttributeSet(DisplayItem* context, const ReferenceAttributeSet& initializerSet); + + void beginAttributeSetRecompute(); + void mergeIn(const ReferenceAttributeSet& other); + void commitAttributeSetRecompute(); + + void executeStateChangers(); + + void fireSets(); + + std::optional getDeclaredInt(ValueKey key); + void setDeclaredInt(const ValueKey key, const double proposed); + + bool checkActive(const int key) + { return attrs[key].checkActive(); } + + std::optional getInt(const int key) const + { return attrs[key].getValue(); } + + void setInteger(const int key, const double proposed) + { attrs[key].setValue(proposed); } + + bool isConst(const int key) + { return attrs[key].checkExprIsConst(); } + + //These don't necessarily have to filter like this, but it's consistent + // and it might make sense someday to filter them with special declare types + // or something. + std::optional getProperty(const int key) + { return attrs[key].getProperty(); } + + std::vector getLiteralList(const int key) + { return attrs[key].getLiteralList(); } + + std::optional getSingleValueKey(const int key) + { return attrs[key].getSingleValueKey(); } + + std::vector getValueKeyList(const int key) //Optional would be more consistent... + { return attrs[key].getValueKeyList(); } + + std::optional getUserToken(const int key) const + { return attrs[key].getUserToken(); } + + std::optional getColor(const int key) + { return attrs[key].getColor(); } +}; + +}//Attributes + +#endif // ATTRIBUTESET_H diff --git a/src/shaping/line.h b/src/shaping/line.h new file mode 100644 index 0000000..73ef992 --- /dev/null +++ b/src/shaping/line.h @@ -0,0 +1,239 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef LINE_H +#define LINE_H + +/* + +namespace IVD +{ +namespace Shaping +{ + +template +Dimens line(Cont theMaterials, const GeometryProposal proposal, const Angle Adjacent) +{ + const int CellCount = theMaterials.size(); + const Angle Opposite = (Adjacent == Angle::Horizontal) ? Angle::Vertical + : Angle::Horizontal; + + const int AvailableAdjacentSpace = proposal.proposedDimensions.get(Adjacent); + const int AvailableOppositeSpace = proposal.proposedDimensions.get(Opposite); + + std::vector greedy; + std::vector shrinky; + + for(Material* material : theMaterials) + { + const FillPrecedence Pref = material->getFillPrecedenceForAngle(Adjacent); + if(Pref == FillPrecedence::Greedy) + greedy.push_back(material); + if(Pref == FillPrecedence::Shrinky) + shrinky.push_back(material); + } + + int usedAdjacentSpace; + int usedOppositeSpace; + + auto resetWorkingAdjacent = [&] + { usedAdjacentSpace = 0; }; + + auto resetWorkingOpposite = [&] + { usedOppositeSpace = 0; }; + + auto resetWorkingDimensions = [&] + { + resetWorkingAdjacent(); + resetWorkingOpposite(); + }; + + resetWorkingDimensions(); + + bool invalidOpposite = false; + + auto applyAdjacentFormulaToChild = [&](Material* material, + GeometryProposal childProposal, + std::function adjacentFormula) + { + //Please leave this here I'm sick of adding it back for debugging + const auto proposedAdjacentSize = adjacentFormula(material); + + //----------Adjacent + childProposal.proposedDimensions.get(Adjacent) = proposedAdjacentSize; + + material->shape(childProposal); + const Dimens childDimens = material->getViewport().d; + + usedAdjacentSpace += childDimens.get(Adjacent); + + //----------Opposite + const int UsedOppositeThisRound = childDimens.get(Opposite); + + if(UsedOppositeThisRound > usedOppositeSpace) + usedOppositeSpace = UsedOppositeThisRound; + + if(AvailableOppositeSpace != UsedOppositeThisRound) + invalidOpposite = true; + + //---------- + assert(childProposal.verifyCompliance(childDimens)); + }; + + auto applyToSet = [&](const std::vector& items, + GeometryProposal childProposal, + std::function formula) + { + for(Material* material : items) + applyAdjacentFormulaToChild(material, childProposal, formula); + }; + + auto adjustOpposite = [&]() + { + if(!invalidOpposite) return; + + //The problem here is that one widget might have actually used all of the opposite + // correctly, while a different one shrank. So really we need a better test to see + // if the widgets didn't all use the same space!!! : TODO, is this still a thing??? + // I don't understand if it's still a problem, doesn't seem like it??? + resetWorkingAdjacent(); + + //We don't discriminate between greedy and shrinky this time, because + // we're only looking to update the child opposites and perhaps + // shrink our adjacent. + for(Material* material : theMaterials) + { + //We've already determined the opposite, so lock both opposite shrink and expand. + //If the opposite is larger, we might recover some adjacent space, + // so we don't shrink lock that dimension. + + //If the opposite is smaller, do we need to lock adjacent shrinking? + //I can't think of anything but the most contrived scenario where + // the adjacent would shrink further. But I also don't see why we + // *have to* lock it... + //(Wouldn't it shrink if we increase the opposite in a vertical text flow scenario?) + //Yeah... Lock it. + + //The computed adjacent is always big enough to handle the computed opposite + // or larger. We won't be suggesting a smaller opposite, so the adjacent should + // not expand further. + + GeometryProposal childProposal = proposal; + childProposal.proposedDimensions = material->getViewport().d; + childProposal.proposedDimensions.get(Opposite) = usedOppositeSpace; + + childProposal.expandForAngle(Opposite) = false; + childProposal.shrinkForAngle(Opposite) = false; + childProposal.expandForAngle(Adjacent) = false; + childProposal.shrinkForAngle(Adjacent) = false; + + material->shape(childProposal); + const Dimens childDimens = material->getViewport().d; + usedAdjacentSpace += childDimens.get(Adjacent); + + assert(childProposal.verifyCompliance(childDimens)); + } + + invalidOpposite = false; + }; + + //Initial pass, get an idea of what the natural sizes are + { + //No explicit expand for opposite because it's taking in the + // higher up suggestion which we much obey anyway. + //Otherwise it throws off the overall opposite rule. + //If it's locked it can't legally grow!!!! Or shrink... No shrinky! + // extra space goes to the child cell anyway. + GeometryProposal childProposal = proposal; + childProposal.expandForAngle(Adjacent) = true; + childProposal.shrinkForAngle(Adjacent) = true; + + auto initalCellSizeFormula = [=](Material*) -> int + { return zeroGuard((AvailableAdjacentSpace - usedAdjacentSpace) / CellCount); }; + + applyToSet(shrinky, childProposal, initalCellSizeFormula); + applyToSet(greedy, childProposal, initalCellSizeFormula); + adjustOpposite(); + } + + + //If the sizes are bad, then we adjust + if(usedAdjacentSpace > proposal.proposedDimensions.get(Adjacent)) + { + if(!proposal.expandForAngleConst(Adjacent)) + { + //No overflow mode, because if we can't expand, we can't expand. + //So it must go to the children. The default is for the whole thing + // to take on the full size, if you want a viewport, put this layout + // inside an item with a viewport layout. + + const int cutSize = (usedAdjacentSpace - AvailableAdjacentSpace) / CellCount; + + auto adjustingSizeFormula = [&](Material* material) -> int + { return zeroGuard(material->getViewport().d.get(Adjacent) - cutSize); }; + + //(╯°□°)╯︵ ┻━┻ + resetWorkingDimensions(); + + GeometryProposal childProposal = proposal; + childProposal.expandForAngle(Adjacent) = false; + + applyToSet(shrinky, childProposal, adjustingSizeFormula); + applyToSet(greedy, childProposal, adjustingSizeFormula); + adjustOpposite(); + }//Else is just whatev's + } + + if(usedAdjacentSpace < proposal.proposedDimensions.get(Adjacent)) + { + if(!proposal.shrinkForAngleConst(Adjacent) && CellCount) + { + //By default we only expand greedy. But if there is no greedy and we MUST expand... + //Actually not sure if this is ever greedy, now that I thonk abut it? Can it be? TODO + auto& theSet = greedy.size() ? greedy + : shrinky; + + auto& opposet = greedy.size() ? shrinky + : greedy; + + const int padSize = (AvailableAdjacentSpace - usedAdjacentSpace) / theSet.size(); + resetWorkingDimensions(); + + auto padFormula = [&](Material* material) -> int + { return material->getViewport().d.get(Adjacent) + padSize; }; + + GeometryProposal childProposal = proposal; + childProposal.shrinkForAngle(Adjacent) = false; + + applyToSet(theSet, childProposal, padFormula); + + //One last problem, we gotta add the opposets to the usedAdjacent and Opposite(?) spaces... + //(This could maybe be abstracted with some code from applyToSet TODO) + for(Material* m : opposet) + { + const Dimens d = m->getViewport().d; + if(d.get(Opposite) > usedOppositeSpace) + { + //Does this ever happen????? TODO + usedOppositeSpace = d.get(Opposite); + invalidOpposite = true; + } + usedAdjacentSpace += d.get(Adjacent); + } + + adjustOpposite(); + } + } + + Dimens usedSpace; + usedSpace.get(Adjacent) = usedAdjacentSpace; + usedSpace.get(Opposite) = usedOppositeSpace; + + return usedSpace; +} + +}//Shaping +}//IVD + +*/ + +#endif // LINE_H diff --git a/src/specific_driver_sdl/cairocanvas.cpp b/src/specific_driver_sdl/cairocanvas.cpp new file mode 100644 index 0000000..b75b58a --- /dev/null +++ b/src/specific_driver_sdl/cairocanvas.cpp @@ -0,0 +1,203 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "cairocanvas.h" + +#include +#include + +#include "assert.h" + +namespace IVD +{ + +cairo_surface_t* createNewCairoSurfaceForPlatform(const Dimens size) +{ return cairo_image_surface_create(CAIRO_FORMAT_RGB24, size.w, size.h); } + +void CairoCanvas::clip() +{ + for(Rect c : clips) + { + cairo_rectangle(myCai, c.c.x, c.c.y, c.d.w, c.d.h); + cairo_clip(myCai); + } +} + +void CairoCanvas::drawWith(Color theColor, + Color::AlphaType alpha, + std::function fun) +{ + cairo_save(myCai); + clip(); + cairo_move_to(myCai, offset.x, offset.y); + cairo_set_source_rgba(myCai, + (theColor.red + 1) / 256.0, + (theColor.green + 1) / 256.0, + (theColor.blue + 1) / 256.0, + (alpha + 1) / 256.0); + fun(myCai); + cairo_restore(myCai); +} + +void CairoCanvas::setSize(const Dimens size) +{ + if(myCai) freeContext(); + + surface = createNewCairoSurfaceForPlatform(size); + myCai = cairo_create(surface); +} + +Dimens CairoCanvas::getSize() +{ + Dimens size; + if(!myCai) return size; + + size.w = cairo_image_surface_get_width(surface); + size.h = cairo_image_surface_get_height(surface); + + return size; +} + + +void CairoCanvas::clear() +{ + //Magic numbers because it's just fucking white, people. + drawWith(Color(255,255,255), 255, [&](cairo_t* cai) + { + cairo_paint(cai); + }); +} + +void CairoCanvas::fillRect(Rect r, Color theColor) +{ + drawWith(theColor, alpha, [&](cairo_t* cai) + { + cairo_rectangle(cai, r.c.x, r.c.y, r.d.w, r.d.h); //Alphabet soup + cairo_fill(cai); + }); +} + + +void CairoCanvas::strokeRect(Rect r, int size, Color theColor, Color::AlphaType alpha) +{ + drawWith(theColor, alpha, [&](cairo_t* cai) + { + cairo_set_line_width(cai, size); + cairo_rectangle(cai, r.c.x - .5, r.c.y - .5, r.d.w, r.d.h); + cairo_stroke(cai); + }); +} + +void CairoCanvas::drawLine(Coords start, Coords end, int size, Color theColor, Color::AlphaType alpha) +{ +} + +void CairoCanvas::drawGradient(Rect box, + Color toftColor, Color::AlphaType toftAlpha, + Color boriColor, Color::AlphaType boriAlpha, Angle theAngle) +{ +assert(false); +} + +void CairoCanvas::drawDropShadow(Rect box, int size, Color theColor, Color::AlphaType ) +{ + assert(false); +} + +void CairoCanvas::drawBitmapRGBoptionalA(Coords dest, int imageStride, int width, int height, int channels, unsigned char* data) +{ + //Goddddddddd we have to rearrange the pixel data ughh... + const auto format = CAIRO_FORMAT_ARGB32; + +#ifdef BIG_ENDIAN_SYSTEM + const int destinationAlphaOffset = 0; + const int destinationRedOffset = 1; + const int destinationGreenOffset = 2; + const int destinationBlueOffset = 3; +#elif LITTLE_ENDIAN_SYSTEM + const int destinationAlphaOffset = 3; + const int destinationRedOffset = 2; + const int destinationGreenOffset = 1; + const int destinationBlueOffset = 0; + +#endif + + const int stride = cairo_format_stride_for_width(format, width); + + const int dstPixelWidth = 4; + const int srcPixelWidth = channels; + + std::vector theTrueNameOfBaal(stride * height * channels); + + for(int row = 0; row != height; ++row) + { + unsigned char* src = data + width * channels * row; + unsigned char* dst = &theTrueNameOfBaal[0] + stride * row; + + int di = 0; + int si = 0; + + while(si != width * channels) + { + //Pixel, *not* subpixel! + unsigned char alpha = dst[di + destinationAlphaOffset] = channels == 4 ? src[si + 3] + : 0xff; + dst[di + destinationRedOffset] = src[si + 0] * -alpha; + dst[di + destinationGreenOffset] = src[si + 1] * -alpha; + dst[di + destinationBlueOffset] = src[si + 2] * -alpha; + //I guess that's how you do premultiplied alpha... + + di += dstPixelWidth; + si += srcPixelWidth; + } + } + + cairo_surface_t* mySurf = cairo_image_surface_create_for_data(&theTrueNameOfBaal[0], + format, + width, + height, + stride); + + cairo_save(myCai); + clip(); + + //Does this need to be offset by .5? + Coords trueDest = dest + offset; + cairo_rectangle(myCai, trueDest.x, trueDest.y, width, height); + cairo_set_source_surface(myCai, mySurf, trueDest.x, trueDest.y); + + + cairo_fill(myCai); + + cairo_restore(myCai); + cairo_surface_destroy(mySurf); + +} + +void CairoCanvas::flush() +{ + cairo_surface_flush(surface); +} + +bool CairoCanvas::ready() +{ + if(myCai) return true; + + assert(!surface); + return false; +} + +Bitmap CairoCanvas::getBitmap() +{ + Bitmap bitmap; + + bitmap.stride = cairo_image_surface_get_stride(surface); + bitmap.width = cairo_image_surface_get_width(surface); + bitmap.height = cairo_image_surface_get_height(surface); + bitmap.data = cairo_image_surface_get_data(surface); + bitmap.channels = 3; //rgb24 see above + + return bitmap; +} + + +}//IVD diff --git a/src/specific_driver_sdl/cairocanvas.h b/src/specific_driver_sdl/cairocanvas.h new file mode 100644 index 0000000..e002eb2 --- /dev/null +++ b/src/specific_driver_sdl/cairocanvas.h @@ -0,0 +1,72 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef CAIROCANVAS_H +#define CAIROCANVAS_H + +#include + +#include "canvas.h" +#include "cairo/cairo.h" + +namespace IVD +{ + +cairo_surface_t* createNewCairoSurfaceForPlatform(const Dimens size); + +class CairoCanvas : public Canvas +{ + cairo_t* myCai; + cairo_surface_t* surface; + + void freeContext() + { + cairo_surface_destroy(surface); + cairo_destroy(myCai); + + surface = nullptr; + myCai = nullptr; + } + + void clip(); + + void drawWith(Color theColor, Color::AlphaType alpha, std::function fun); + +public: + CairoCanvas(): myCai(nullptr), surface(nullptr) {} + ~CairoCanvas() { freeContext(); } + + void setSize(const Dimens size) final; + Dimens getSize() final; + bool ready(); + + void clear(); + + void fillRect(Rect r, Color theColor) final; + void strokeRect(Rect r, int size, Color theColor, Color::AlphaType alpha) final; + void drawLine(Coords start, Coords end, int size, Color theColor, Color::AlphaType alpha) final; + void drawGradient(Rect box, + Color toftColor, + Color::AlphaType toftAlpha, + Color boriColor, + Color::AlphaType boriAlpha, + Angle theAngle) final; + void drawDropShadow(Rect box, int size, Color theColor, Color::AlphaType alpha) final; + + virtual void drawBitmapRGBoptionalA(Coords dest, + int stride, + int width, + int height, + int channels, + unsigned char* data) final; + + //Defined in */TextDriver.cpp + void drawText(Coords origin, const std::string text, DisplayItem* style) final; + + void flush() final; + + Bitmap getBitmap() final; +}; + +}//IVD + +#endif // CAIROCANVAS_H diff --git a/src/specific_driver_sdl/sdldriver.cpp b/src/specific_driver_sdl/sdldriver.cpp new file mode 100644 index 0000000..8f05dc9 --- /dev/null +++ b/src/specific_driver_sdl/sdldriver.cpp @@ -0,0 +1,683 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "specific_driver_sdl/sdldriver.h" + +#include "states.h" +#include "defaults.h" +#include "statemanager.h" + +#include + + +namespace IVD +{ + +SDLdriver::SDLdriver(): + rootWithMouseFocus(nullptr), + hoverInvalidated(true) +{ + int code = SDL_Init(SDL_INIT_VIDEO); + assert(code >= 0); + + reprodyne_open_scope(this); +} + +SDLdriver::~SDLdriver() +{ + SDL_Quit(); +} + +Driver* createDefaultDriver() +{ return new SDLdriver; } + +void SDLdriver::invalidateHover(const Coords point, const Uint32 windowID) +{ + hoverInvalidated = true; + DisplayItem* myRoot = nullptr; + + for(const auto& pair : pairs) + { + if(!pair.second.window) continue; + if(pair.second.window->getWindowId() == windowID) + { + myRoot = pair.first; + break; + } + } + + getStateManager()->mutateAll(States::Window::MouseFocus, false); + if(myRoot) + getStateManager()->mutateIfObserved(StateKey(States::Window::MouseFocus, myRoot), true); + + rootWithMouseFocus = myRoot; +} + +void SDLdriver::invalidateHoverDesperately() +{ + Coords makeShiftMousePosition; + SDL_GetMouseState(&makeShiftMousePosition.x, &makeShiftMousePosition.y); + + makeShiftMousePosition.x = reprodyne_intercept_double(this, "makeshift-mouse-x", makeShiftMousePosition.x); + makeShiftMousePosition.y = reprodyne_intercept_double(this, "makeshift-mouse-y", makeShiftMousePosition.y); + const auto windowId = reprodyne_intercept_double(this, + "makeshift-mouse-position-window-id", + SDL_GetWindowID(SDL_GetMouseFocus())); + + invalidateHover(makeShiftMousePosition, windowId); +} + +void SDLdriver::addDisplayItem(DisplayItem *item) +{ + auto& pair = pairs[item]; + + if(!pair.window) + { + pair.window = std::make_unique(item); + getStateManager()->setTriggerIfObserved(StateKey(States::Window::Initialized, item)); + } + + pair.destroy = false; +} + +void SDLdriver::removeDisplayItem(DisplayItem* item) +{ + auto it = pairs.find(item); + if(it == pairs.end()) return; + + it->second.destroy = true; +} + +void SDLdriver::processEvents() +{ + StateManager* stateManager = getStateManager(); + auto mapSDLscanCodeToStateSymbol = [&](const Uint16 scanCode) -> std::optional + { + std::optional result; + + switch(scanCode) + { + case SDL_SCANCODE_AUDIOPLAY: return States::Key::Play; + case SDL_SCANCODE_AUDIOSTOP: return States::Key::Stop; + case SDL_SCANCODE_AUDIONEXT: return States::Key::Next; + case SDL_SCANCODE_AUDIOPREV: return States::Key::Previous; + + case SDL_SCANCODE_LGUI: return States::Key::LeftSuper; + case SDL_SCANCODE_LSHIFT: return States::Key::LeftShift; + case SDL_SCANCODE_LCTRL: return States::Key::LeftCtrl; + case SDL_SCANCODE_LALT: return States::Key::LeftAlt; + + case SDL_SCANCODE_RGUI: return States::Key::RightSuper; + case SDL_SCANCODE_RSHIFT: return States::Key::RightShift; + case SDL_SCANCODE_RCTRL: return States::Key::RightCtrl; + case SDL_SCANCODE_RALT: return States::Key::RightAlt; + + case SDL_SCANCODE_CAPSLOCK: return States::Key::CapsLock; + //Alt G + case SDL_SCANCODE_MENU: return States::Key::Menu; + + case SDL_SCANCODE_PRINTSCREEN: return States::Key::PrintScreen; + case SDL_SCANCODE_SCROLLLOCK: return States::Key::ScrollLock; + case SDL_SCANCODE_PAUSE: return States::Key::Pause; + + case SDL_SCANCODE_INSERT: return States::Key::Insert; + case SDL_SCANCODE_DELETE: return States::Key::Delete; + case SDL_SCANCODE_HOME: return States::Key::Home; + case SDL_SCANCODE_END: return States::Key::End; + case SDL_SCANCODE_PAGEUP: return States::Key::PageUp; + case SDL_SCANCODE_PAGEDOWN: return States::Key::PageDown; + + case SDL_SCANCODE_LEFT: return States::Key::LeftArrow; + case SDL_SCANCODE_RIGHT: return States::Key::RightArrow; + case SDL_SCANCODE_UP: return States::Key::UpArrow; + case SDL_SCANCODE_DOWN: return States::Key::DownArrow; + + case SDL_SCANCODE_NUMLOCKCLEAR: return States::Key::NumLock; + + case SDL_SCANCODE_ESCAPE: return States::Key::Escape; //Must be Italian + + case SDL_SCANCODE_F1: return States::Key::F1; + case SDL_SCANCODE_F2: return States::Key::F2; + case SDL_SCANCODE_F3: return States::Key::F3; + case SDL_SCANCODE_F4: return States::Key::F4; + case SDL_SCANCODE_F5: return States::Key::F5; + case SDL_SCANCODE_F6: return States::Key::F6; + case SDL_SCANCODE_F7: return States::Key::F7; + case SDL_SCANCODE_F8: return States::Key::F8; + case SDL_SCANCODE_F9: return States::Key::F9; + case SDL_SCANCODE_F10: return States::Key::F10; + case SDL_SCANCODE_F11: return States::Key::F11; + case SDL_SCANCODE_F12: return States::Key::F12; + + case SDL_SCANCODE_GRAVE: return States::Key::Tilde; + case SDL_SCANCODE_1: return States::Key::RowNum1; + case SDL_SCANCODE_2: return States::Key::RowNum2; + case SDL_SCANCODE_3: return States::Key::RowNum3; + case SDL_SCANCODE_4: return States::Key::RowNum4; + case SDL_SCANCODE_5: return States::Key::RowNum5; + case SDL_SCANCODE_6: return States::Key::RowNum6; + case SDL_SCANCODE_7: return States::Key::RowNum7; + case SDL_SCANCODE_8: return States::Key::RowNum8; + case SDL_SCANCODE_9: return States::Key::RowNum9; + case SDL_SCANCODE_0: return States::Key::RowNum0; + case SDL_SCANCODE_MINUS: return States::Key::Minus; + case SDL_SCANCODE_EQUALS: return States::Key::Equals; + case SDL_SCANCODE_BACKSPACE: return States::Key::Backspace; + + case SDL_SCANCODE_TAB: return States::Key::Tab; + case SDL_SCANCODE_Q: return States::Key::Q; + case SDL_SCANCODE_W: return States::Key::W; + case SDL_SCANCODE_E: return States::Key::E; + case SDL_SCANCODE_R: return States::Key::R; + case SDL_SCANCODE_T: return States::Key::T; + case SDL_SCANCODE_Y: return States::Key::Y; + case SDL_SCANCODE_U: return States::Key::U; + case SDL_SCANCODE_I: return States::Key::I; + case SDL_SCANCODE_O: return States::Key::O; + case SDL_SCANCODE_P: return States::Key::P; + case SDL_SCANCODE_LEFTBRACKET: return States::Key::LeftBracket; + case SDL_SCANCODE_RIGHTBRACKET: return States::Key::RightBracket; + case SDL_SCANCODE_BACKSLASH: return States::Key::Backslash; + + + case SDL_SCANCODE_A: return States::Key::A; + case SDL_SCANCODE_S: return States::Key::S; + case SDL_SCANCODE_D: return States::Key::D; + case SDL_SCANCODE_F: return States::Key::F; + case SDL_SCANCODE_G: return States::Key::G; + case SDL_SCANCODE_H: return States::Key::H; + case SDL_SCANCODE_J: return States::Key::J; + case SDL_SCANCODE_K: return States::Key::K; + case SDL_SCANCODE_L: return States::Key::L; + case SDL_SCANCODE_SEMICOLON: return States::Key::Semicolon; + case SDL_SCANCODE_APOSTROPHE: return States::Key::Apostrophe; + case SDL_SCANCODE_RETURN: return States::Key::Return; + + case SDL_SCANCODE_Z: return States::Key::Z; + case SDL_SCANCODE_X: return States::Key::X; + case SDL_SCANCODE_C: return States::Key::C; + case SDL_SCANCODE_V: return States::Key::V; + case SDL_SCANCODE_B: return States::Key::B; + case SDL_SCANCODE_N: return States::Key::N; + case SDL_SCANCODE_M: return States::Key::M; + case SDL_SCANCODE_COMMA: return States::Key::Comma; + case SDL_SCANCODE_PERIOD: return States::Key::Period; + case SDL_SCANCODE_SLASH: return States::Key::Slash; + + case SDL_SCANCODE_SPACE: return States::Key::Spacebar; + + case SDL_SCANCODE_KP_DIVIDE: return States::Key::Pad::Slash; + case SDL_SCANCODE_KP_MULTIPLY: return States::Key::Pad::Star; + case SDL_SCANCODE_KP_MINUS: return States::Key::Pad::Minus; + + case SDL_SCANCODE_KP_7: return States::Key::Pad::Num7; + case SDL_SCANCODE_KP_8: return States::Key::Pad::Num8; + case SDL_SCANCODE_KP_9: return States::Key::Pad::Num9; + case SDL_SCANCODE_KP_PLUS: return States::Key::Pad::Plus; + + case SDL_SCANCODE_KP_4: return States::Key::Pad::Num4; + case SDL_SCANCODE_KP_5: return States::Key::Pad::Num5; + case SDL_SCANCODE_KP_6: return States::Key::Pad::Num6; + case SDL_SCANCODE_KP_TAB: return States::Key::Pad::Tab; + + case SDL_SCANCODE_KP_1: return States::Key::Pad::Num1; + case SDL_SCANCODE_KP_2: return States::Key::Pad::Num2; + case SDL_SCANCODE_KP_3: return States::Key::Pad::Num3; + + case SDL_SCANCODE_KP_0: return States::Key::Pad::Num0; + case SDL_SCANCODE_KP_PERIOD: return States::Key::Pad::Period; + case SDL_SCANCODE_KP_ENTER: return States::Key::Pad::Return; + + default: return result; + } + }; + + auto mapSDLmouseButtonToSymbol = [&](const Uint8 button) -> States::Symbol + { + switch(button) + { + case SDL_BUTTON_LEFT: return States::Mouse::ButtonLeft; + case SDL_BUTTON_MIDDLE: return States::Mouse::ButtonMiddle; + case SDL_BUTTON_RIGHT: return States::Mouse::ButtonRight; + case SDL_BUTTON_X1: return States::Mouse::ButtonFour; + case SDL_BUTTON_X2: return States::Mouse::ButtonFive; + default: assert(false); + } + }; + + //TODO: Would it be faster to do a search over the states and then see if it matches + // the event, rather than search and see if the state is observed? + + SDL_Event event; + if(reprodyne_intercept_double(this, "poll-event", SDL_PollEvent(&event))) + { + const double eventType = reprodyne_intercept_double(this, "event-type", event.type); + + switch(int(eventType)) + { + case SDL_KEYDOWN: + case SDL_KEYUP: + { + const auto scancode = reprodyne_intercept_double(this, "scancode", event.key.keysym.scancode); + + const auto symbol = mapSDLscanCodeToStateSymbol(scancode); + if(symbol) + { + if(eventType == SDL_KEYDOWN) + stateManager->setTriggerIfObserved(States::getButtonPress(*symbol)); + else if(eventType == SDL_KEYUP) + stateManager->setTriggerIfObserved(States::getButtonRelease(*symbol)); + } + break; + } + + case SDL_MOUSEBUTTONDOWN: + { + const auto keysym = reprodyne_intercept_double(this, "mouse-button-down", event.button.button); + + States::Symbol sym = mapSDLmouseButtonToSymbol(keysym); + stateManager->setTriggerIfObserved(States::getButtonPress(sym)); + break; + } + case SDL_MOUSEBUTTONUP: //Bucko + { + const auto keysym = reprodyne_intercept_double(this, "mouse-button-up", event.button.button); + + States::Symbol sym = mapSDLmouseButtonToSymbol(keysym); + stateManager->setTriggerIfObserved(States::getButtonRelease(sym)); + break; + } + case SDL_MOUSEMOTION: + { + stateManager->setTriggerIfObserved(States::Mouse::Motion); + + mousePointWindow.x = reprodyne_intercept_double(this, "mouse-motion-x", event.motion.x); + mousePointWindow.y = reprodyne_intercept_double(this, "mouse-motion-y", event.motion.y); + + const auto windowId = reprodyne_intercept_double(this, "motion-window-id", event.motion.windowID); + + invalidateHover(Coords(mousePointWindow.x, mousePointWindow.y), windowId); + break; + } + case SDL_MOUSEWHEEL: + { + //Doesn't necessarily invalidate hover + stateManager->setTriggerIfObserved(States::Mouse::Wheel); + + if(event.wheel.direction == SDL_MOUSEWHEEL_NORMAL) + { + scrollDist.x = event.wheel.x; + scrollDist.y = event.wheel.y; + } + else if(event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) + { + scrollDist.x = event.wheel.x * -1; + scrollDist.y = event.wheel.y * -1; + } + + //All that matters as far as the harness is concerned, is what the result is + // we'll get to these two lines in either mode. + scrollDist.x = reprodyne_intercept_double(this, "scroll-dist-x", scrollDist.x); + scrollDist.y = reprodyne_intercept_double(this, "scroll-dist-y", scrollDist.y); + break; + } + case SDL_WINDOWEVENT: + { + for(const auto& pair : pairs) + { + if(!pair.second.window) + continue; + + DisplayItem* item = pair.first; + + const auto currentRootWindowId = pair.second.window->getWindowId(); + const auto eventWindowId = reprodyne_intercept_double(this, + "window-event-window-id", + event.window.windowID); + + if(currentRootWindowId != eventWindowId) continue; + + switch(int(reprodyne_intercept_double(this, "window-event", event.window.event))) + { + case SDL_WINDOWEVENT_SIZE_CHANGED: + { + //If in top down mode + if(Default::Filter::getWindowSizeStrategy(item) == Property::TopDown) + invalidateGeometry(item); + + //If the canvas is updated too soon after the window is resized, then the full + // area isn't drawn and there will be a black area (if larger than before) + // once the window system responds to the resize request. But we still get a + // resize event, so we push another redraw. + invalidateCanvas(item); + break; + } + + case SDL_WINDOWEVENT_MOVED: + invalidatePosition(item); + break; + + case SDL_WINDOWEVENT_CLOSE: + stateManager->setTriggerIfObserved(StateKey(States::Window::CloseRequest, item)); + break; + + case SDL_WINDOWEVENT_LEAVE: + case SDL_WINDOWEVENT_ENTER: + invalidateHoverDesperately(); + break; + + case SDL_WINDOWEVENT_SHOWN: + invalidateCanvas(item); + stateManager->setTriggerIfObserved(StateKey(States::Window::Shown, item)); + break; + } + } + break; + } + case SDL_QUIT: + { + stateManager->setTriggerIfObserved(States::App::CloseApp); + break; + } + } + } + + + //Button states are out of sync with events by definition. + { + int length; + const Uint8* keyStates = SDL_GetKeyboardState(&length); + + for(Uint16 i = 0; i != length; ++i) + { + auto symbol = mapSDLscanCodeToStateSymbol(i); + if(!symbol) continue; + + const StateKey theStateKey(States::getButtonActive(*symbol)); + const bool pressed = reprodyne_intercept_double(this, "key-state", keyStates[i]); + + stateManager->mutateIfObserved(theStateKey, pressed); + } + } + { + const Uint32 mask = reprodyne_intercept_double(this, + "mouse-mask", + SDL_GetMouseState(&queriedMousePoint.x, + &queriedMousePoint.y)); + + const StateKey left(States::getButtonActive(States::Mouse::ButtonLeft)); + const StateKey middle(States::getButtonActive(States::Mouse::ButtonMiddle)); + const StateKey right(States::getButtonActive(States::Mouse::ButtonRight)); + const StateKey four(States::getButtonActive(States::Mouse::ButtonFour)); + const StateKey five(States::getButtonActive(States::Mouse::ButtonFive)); + + auto thingamajigulator = [&](const auto code, const StateKey stateKey) + { + if((mask & code) == code) + stateManager->mutateIfObserved(stateKey, true); + else + stateManager->mutateIfObserved(stateKey, false); + }; + + thingamajigulator(SDL_BUTTON_LMASK, left); + thingamajigulator(SDL_BUTTON_MMASK, middle); + thingamajigulator(SDL_BUTTON_RMASK, right); + thingamajigulator(SDL_BUTTON_X1MASK, four); + thingamajigulator(SDL_BUTTON_X2MASK, five); + } +} + +void SDLdriver::invalidateGeometry(DisplayItem *item) +{ + if(pairs.count(item->getRoot())) + pairs.at(item->getRoot()).invalidGeometry = true; +} + +void SDLdriver::invalidatePosition(DisplayItem* item) +{ + auto root = item->getRoot(); + if(root == item) + { + pairs.at(root).invalidPosition = true; + } + else invalidateGeometry(item); +} + +void SDLdriver::invalidateCanvas(DisplayItem *item) +{ + if(pairs.count(item->getRoot())) + pairs.at(item->getRoot()).invalidCanvas = true; +} + +void SDLdriver::invalidateTitleText(DisplayItem *item) +{ + auto it = pairs.find(item); + if(it == pairs.end()) return; + + it->second.invalidTitleText = true; +} + +void SDLdriver::invalidateVisibility(DisplayItem *item) +{ + if(item->getRoot() == item) + pairs[item].invalidVisibility = true; + else + invalidateCanvas(item->getRoot()); +} + +void SDLdriver::refresh() +{ + hoverInvalidated = false; + //That kinda thing. + + for(auto it = pairs.begin(); it != pairs.end(); /*nope*/) + { + DisplayItem* item = it->first; + auto& pairData = it->second; + + if(pairData.destroy) + pairData.window.reset(); + + if(!pairData.window) + { + it = pairs.erase(it); + continue; + } + + SDLwindow* window = pairData.window.get(); + + const int displayIndex = reprodyne_intercept_double(this, "display-index", window->getDisplayIndex()); + + SDL_Rect displayUsable; + SDL_Rect displayFull; + + SDL_GetDisplayBounds(displayIndex, &displayFull); + SDL_GetDisplayUsableBounds(displayIndex, &displayUsable); + + displayUsable.w = reprodyne_intercept_double(this, "display-usable-w", displayUsable.w); + displayUsable.h = reprodyne_intercept_double(this, "display-usable-h", displayUsable.h); + displayUsable.x = reprodyne_intercept_double(this, "display-usable-x", displayUsable.x); + displayUsable.y = reprodyne_intercept_double(this, "display-usable-y", displayUsable.y); + + displayFull.w = reprodyne_intercept_double(this, "display-full-w", displayFull.w); + displayFull.h = reprodyne_intercept_double(this, "display-full-h", displayFull.h); + displayFull.x = reprodyne_intercept_double(this, "display-full-x", displayFull.x); + displayFull.y = reprodyne_intercept_double(this, "display-full-y", displayFull.y); + + + + if(pairData.invalidGeometry) + { + //We just assume any time geometry is invalidated, resizability is as well. + pairData.window->setResizable(Default::Filter::checkResizable(item)); + + + const bool fullscreen = Default::Filter::getFullscreen(item); + + GeometryProposal prop; + + //This isn't a great way to handle this. How do we set fullscreen? + //How do we know what the true bounds are? (There is a "fake" fullscreen mode) + if(fullscreen) + { + prop.proposedDimensions.get(Angle::Horizontal) = displayFull.w; + prop.proposedDimensions.get(Angle::Vertical) = displayFull.h; + + //The default is all false for the expand/shrink flags + } + else + { + //if in top-down mode, then get the window size and lock both + // dimensions. Else, in order, use window-size-hint, then + // starting size. Normal size expressions will lock the + // viewport in the material, we don't need them here. + prop.proposedDimensions.get(Angle::Adjacent) = displayUsable.w; + prop.proposedDimensions.get(Angle::Opposite) = displayUsable.h; + + const int sizeStrategy = Default::Filter::getWindowSizeStrategy(item); + + if(sizeStrategy == Property::BottomUp) + { + prop.shrinkForAngle(Angle::Vertical) = true; + prop.shrinkForAngle(Angle::Horizontal) = true; + } + else + { + //Use the current window size if initial not set. Relying on the + // user to make sure there are initial sizes on first pass... + + const Dimens currentSize = window->getClientArea(); + + prop.proposedDimensions.get(Angle::Adjacent) = currentSize.get(Angle::Adjacent); + prop.proposedDimensions.get(Angle::Opposite) = currentSize.get(Angle::Opposite); + } + } + + item->shape(prop); + item->computerAbsoluteOffsets(Coords()); + + invalidateHoverDesperately(); + + const Dimens drawableDimens = item->getViewportDimens(); + + if(!pairData.previousSizeSet || pairData.previousDimens != drawableDimens) + { + window->setClientArea(drawableDimens); + pairData.previousDimens = drawableDimens; + pairData.previousSizeSet = true; + + //Rely on window size change event invalidating the canvas again, otherwise the update + // is too soon and the window system hasn't responded to the resize request yet. + } + else pairData.invalidCanvas = true; + + //Does this make it so that the surface doesn't "jiggle" by one pixel on resizes? + pairData.invalidCanvas = true; + + pairData.invalidPosition = true; + pairData.invalidGeometry = false; + } + + if(pairData.invalidPosition) + { + //We don't use SDL_WINDOWPOS_CENTERED because we can't compare + // it to the current position of the window for flag invalidation. + //Looking at the SDL source code, it doesn't make any special calls or + // anything, it just does a basic centering equation. + //Also, it uses DisplayBounds and not UsableDisplayBound like I would + // like. So... + //ALSO: Nothing fancy for different screens, etc. TODO + + + + + // + + //This doesn't really work, because if it's still grabbed, then the + // resize doesn't happen, and an event is not generated. And nothing is + // generated once the mouse is released... Shit. It only really works + // with constant polling. + + /* + if(window->getWindowPos() == theNewPosition) + { + invalidateHoverDesperately(); //AFTER we move the window (shouldn't matter tho?) + pairData.invalidPosition = false; + } + */ + //else, we couldn't set the position. Probably because the window is still grabbed. + // In this case we just spin until the user lets go. Hopefully no other position puts us + // in this state, such as attempting to set the position whilst fullscreen. + //(still doesn't work TODO) + } + + if(pairData.invalidCanvas) + { + if(window->getCanvas()->ready()) + { + auto canvas = window->getCanvas(); + canvas->clear(); + + currentCanvas = canvas; + item->render(); + + window->present(); + } + + pairData.invalidCanvas = false; + } + + if(pairData.invalidTitleText) + { + window->setTitleText(Default::Filter::getTitleText(item)); + pairData.invalidTitleText = false; + } + + if(pairData.invalidResizability) + { + window->setResizable(Default::Filter::checkResizable(item)); + pairData.invalidResizability = false; + } + + if(pairData.invalidVisibility) + { + window->setVisible(Default::Filter::getVisibility(item)); + pairData.invalidVisibility = false; + } + + { + const Bitmap bipbap = window->getCanvas()->getBitmap(); + reprodyne_validate_bitmap_hash(item, "bitmap", + bipbap.width * bipbap.channels, + bipbap.height, + bipbap.stride, + bipbap.data); + } + + ++it; + } +} + +bool SDLdriver::checkAnythingToDo() +{ + if(hoverInvalidated) + return true; + + if(reprodyne_intercept_double(this, "has-event", SDL_HasEvents(SDL_FIRSTEVENT, SDL_LASTEVENT))) + return true; + + + for(auto& pair : pairs) + { + auto& data = pair.second; + + //Is data.destroy ever true here? This should get run right after refresh which handles that... + // Of course it handles everything else, too... Except invalidPosition, sometimes. + if(data.destroy || data.invalidCanvas || data.invalidGeometry || data.invalidPosition || + data.invalidTitleText || data.invalidVisibility || data.invalidResizability) + return true; + } + + return false; +} + +}//IVD diff --git a/src/specific_driver_sdl/sdldriver.h b/src/specific_driver_sdl/sdldriver.h new file mode 100644 index 0000000..83b945d --- /dev/null +++ b/src/specific_driver_sdl/sdldriver.h @@ -0,0 +1,97 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef SDLDRIVER_H +#define SDLDRIVER_H + +#include "driver.h" +#include "specific_driver_sdl/sdlwindow.h" + +#include + +namespace IVD +{ + +class SDLdriver : public Driver +{ + struct ItemWindowPair + { + std::unique_ptr window; + bool destroy; + + bool invalidGeometry; + bool invalidPosition; + bool invalidCanvas; + + bool invalidTitleText; + bool invalidResizability; + bool invalidVisibility; + //Fullscreen....... too tired do it later + + //Not using std::optional here because for some reason it doesn't + // copy the structure properly... + bool previousSizeSet; + Dimens previousDimens; + + ItemWindowPair(): + destroy(false), + invalidGeometry(true), + invalidPosition(true), + invalidCanvas(true), + invalidTitleText(true), + invalidResizability(true), + invalidVisibility(true), + previousSizeSet(false) + {} + }; + + std::map pairs; + + DisplayItem* rootWithMouseFocus; + //Coords mouseHoverPoint; + Coords mousePointWindow; + Coords scrollDist; + + Coords queriedMousePoint; + + Canvas* currentCanvas; + + bool hoverInvalidated; + + void invalidateHover(const Coords point, const Uint32 windowID); + void invalidateHoverDesperately(); + +public: + SDLdriver(); + ~SDLdriver(); + + void addDisplayItem(DisplayItem* item); + void removeDisplayItem(DisplayItem* item); + void processEvents(); + + void invalidateGeometry(DisplayItem* item); + void invalidatePosition(DisplayItem* item); + void invalidateCanvas(DisplayItem* item); + + void invalidateTitleText(DisplayItem* item); + void invalidateVisibility(DisplayItem* item); + + void refresh(); + + bool checkAnythingToDo(); + + bool checkHoverInvalidated() + { return hoverInvalidated; } + + Canvas* getCanvas() + { return currentCanvas; } + + DisplayItem* getWindowItemWithMouseFocus() + { return rootWithMouseFocus; } + + Coords getMousePointRelativeToWindow() + { return mousePointWindow; } +}; + +}//IVD + +#endif // SDLDRIVER_H diff --git a/src/specific_driver_sdl/sdlwindow.cpp b/src/specific_driver_sdl/sdlwindow.cpp new file mode 100644 index 0000000..76ed0ed --- /dev/null +++ b/src/specific_driver_sdl/sdlwindow.cpp @@ -0,0 +1,221 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "sdlwindow.h" + +#include "defaults.h" + +static IVD::Dimens getDrawableAreaForRenderer(SDL_Renderer* theRenderer) +{ + IVD::Dimens drawable; + SDL_GetRendererOutputSize(theRenderer, &drawable.w, &drawable.h); + + return drawable; +} + +void SDLwindow::createWindow(const std::string title, const IVD::Rect theRect, const int flags) +{ + myWindow = SDL_CreateWindow(title.c_str(), theRect.c.x, theRect.c.y, theRect.d.w, theRect.d.h, flags); + + assert(myWindow); + + myRenderer = SDL_CreateRenderer(myWindow, -1, SDL_RENDERER_ACCELERATED | + SDL_RENDERER_PRESENTVSYNC); + + assert(myRenderer); +} + +void SDLwindow::destroyWindow() +{ + SDL_DestroyRenderer(myRenderer); + SDL_DestroyWindow(myWindow); + + myRenderer = nullptr; + myWindow = nullptr; +} + +SDLwindow::SDLwindow(IVD::DisplayItem* item): + myFormat(SDL_AllocFormat(SDL_PIXELFORMAT_RGB24)) +{ + int flags = SDL_WINDOW_HIDDEN; + + if(IVD::Default::Filter::checkResizable(item)) + flags |= SDL_WINDOW_RESIZABLE; + + createWindow("", IVD::Rect(), flags); +} + +SDLwindow::~SDLwindow() +{ + SDL_FreeFormat(myFormat); + destroyWindow(); + //bye bye :< +} + +IVD::Dimens SDLwindow::getClientArea() +{ + IVD::Dimens myDimens; + SDL_GetWindowSize(myWindow, &myDimens.w, &myDimens.h); + + return myDimens; +} + +IVD::Dimens SDLwindow::getWindowArea() +{ + int top; + int left; + int bottom; + int right; + + SDL_GetWindowBordersSize(myWindow, &top, &left, &bottom, &right); + + IVD::Dimens myDimens = getClientArea(); + myDimens.h += top + bottom; + myDimens.w += left + right; + + return myDimens; +} + +IVD::Coords SDLwindow::getWindowPos() +{ + IVD::Coords myCoords; + SDL_GetWindowPosition(myWindow, &myCoords.x, &myCoords.y); + return myCoords; +} + +void SDLwindow::setClientArea(const IVD::Dimens size) +{ + { + Uint32 flags = SDL_GetWindowFlags(myWindow); + + if((flags & SDL_WINDOW_FULLSCREEN) != SDL_WINDOW_FULLSCREEN && + (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP) + { + SDL_SetWindowSize(myWindow, size.w, size.h); + } + } + + myCanvas.setSize(size); +} + +void SDLwindow::setWindowPos(const IVD::Coords pos) +{ + //uh.. What about decorations? + //Well. According to https://bugzilla.libsdl.org/show_bug.cgi?id=3845 + //These SDL_*WindowPosition functions return the position of the viewport, and + // not the window decorations. This is a bug in SDL. Ideally, this + // next call would correct for the decoration offset. TODO + //coordinateOffset = pos + SDL_GetWindowBordersSize() values. + SDL_SetWindowPosition(myWindow, pos.x, pos.y); +} + +void SDLwindow::setResizable(const bool flag) +{ + int flags = SDL_GetWindowFlags(myWindow); + + if(flag != ((flags & SDL_WINDOW_RESIZABLE) == SDL_WINDOW_RESIZABLE)) + { + if(!flag) flags &= ~SDL_WINDOW_RESIZABLE; + else flags |= SDL_WINDOW_RESIZABLE; + + assert(flag == ((flags & SDL_WINDOW_RESIZABLE) == SDL_WINDOW_RESIZABLE)); + + IVD::Rect windowRect; + windowRect.d = getClientArea(); + windowRect.c = getWindowPos(); + const std::string title = SDL_GetWindowTitle(myWindow); + + destroyWindow(); + createWindow(title, windowRect, flags); + + setClientArea(windowRect.d); + } +} + +void SDLwindow::setVisible(const bool flag) +{ + if(flag) + SDL_ShowWindow(myWindow); + else + SDL_HideWindow(myWindow); +} + +void SDLwindow::setTitleText(const std::string title) +{ + SDL_SetWindowTitle(myWindow, title.c_str()); +} + +void SDLwindow::present() +{ + IVD::Dimens myDimens = getDrawableAreaForRenderer(myRenderer); + + //Crashes if less than 2 because... Pixel offsets? Sorry too busy to figure out. + if(myDimens.w < 2 || myDimens.h < 2) return; + + const int height = myDimens.h; + const int widthInPixels = myDimens.w; + + void* destData; + int destPitch; + + SDL_Texture* myTarget = SDL_CreateTexture(myRenderer, + myFormat->format, + SDL_TEXTUREACCESS_STREAMING, + myDimens.w, + myDimens.h); + SDL_LockTexture(myTarget, nullptr, &destData, &destPitch); + + myCanvas.flush(); + + IVD::Bitmap mySurface = myCanvas.getBitmap(); + + unsigned char* sourceData = mySurface.data; + const int sourcePitch = mySurface.stride; + + auto mapMaskToOffset = [](const Uint32 mask) + { + if(mask == 0x000000ff) return 0; + else if(mask == 0x0000ff00) return 1; + else if(mask == 0x00ff0000) return 2; + else if(mask == 0xff000000) return 3; + + assert(false); + }; + + const int destRedOffset = mapMaskToOffset(myFormat->Rmask); + const int destGreenOffset = mapMaskToOffset(myFormat->Gmask); + const int destBlueOffset = mapMaskToOffset(myFormat->Bmask); + const int pixelWidthSDL = myFormat->BytesPerPixel; + + //As per the documentation for CAIRO_FORMAT_RGB24 + const int sourceRedOffset = 2; + const int sourceGreenOffset = 1; + const int sourceBlueOffset = 0; + const int pixelWidthCairo = 4; + + const int destDataWidth = widthInPixels * pixelWidthSDL; + const int sourceDataWidth = widthInPixels * pixelWidthCairo; + + for(int row = 0; row != height; ++row) + { + unsigned char* dest = (unsigned char*)destData + (destPitch * row); + unsigned char* source = sourceData + (sourcePitch * row); + + int di = 0; + int si = 0; + + while(di != destDataWidth && si != sourceDataWidth) + { + dest[di + destRedOffset] = source[si + sourceRedOffset]; + dest[di + destGreenOffset] = source[si + sourceGreenOffset]; + dest[di + destBlueOffset] = source[si + sourceBlueOffset]; + + di += pixelWidthSDL; + si += pixelWidthCairo; + } + } + + SDL_UnlockTexture(myTarget); + SDL_RenderCopy(myRenderer, myTarget, nullptr, nullptr); + SDL_DestroyTexture(myTarget); + SDL_RenderPresent(myRenderer); +} diff --git a/src/specific_driver_sdl/sdlwindow.h b/src/specific_driver_sdl/sdlwindow.h new file mode 100644 index 0000000..67f131e --- /dev/null +++ b/src/specific_driver_sdl/sdlwindow.h @@ -0,0 +1,52 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef SDLWINDOW_H +#define SDLWINDOW_H + +#include "SDL2/SDL.h" + +#include "geometry.h" +#include "specific_driver_sdl/cairocanvas.h" + +class SDLwindow +{ + SDL_Window* myWindow; + SDL_Renderer* myRenderer; + + SDL_PixelFormat* myFormat; + + IVD::CairoCanvas myCanvas; + + void* context; + + void createWindow(const std::string title, const IVD::Rect theRect, const int flags); + void destroyWindow(); + +public: + SDLwindow(IVD::DisplayItem* item); + ~SDLwindow(); + + int getWindowId() + { return SDL_GetWindowID(myWindow); } + + int getDisplayIndex() + { return SDL_GetWindowDisplayIndex(myWindow); } + + IVD::Dimens getClientArea(); + IVD::Dimens getWindowArea(); + IVD::Coords getWindowPos(); + + void setClientArea(const IVD::Dimens size); + void setWindowPos(const IVD::Coords pos); + + void setResizable(const bool flag); + void setVisible(const bool flag); + void setTitleText(const std::string title); + + IVD::Canvas* getCanvas() + { return &myCanvas; } + + void present(); +}; + +#endif // SDLWINDOW_H diff --git a/src/specific_driver_sdl/textdriver.cpp b/src/specific_driver_sdl/textdriver.cpp new file mode 100644 index 0000000..bc849f2 --- /dev/null +++ b/src/specific_driver_sdl/textdriver.cpp @@ -0,0 +1,272 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "text.h" + +#include "harfbuzz/hb.h" +#include "harfbuzz/hb-ft.h" + +#include "specific_driver_sdl/cairocanvas.h" +#include "cairo/cairo.h" +#include "cairo/cairo-ft.h" + +#include + +namespace IVD +{ + +namespace Text +{ + +class RunWrapper +{ + Dimens myDimens; + Coords bearings; + const Angle itemFlowAngle; + + static FT_Library myFtLib; + hb_buffer_t* harfBuzzBuffer; + unsigned int glyphCount; + hb_glyph_info_t* glyphInfo; + hb_glyph_position_t* glyphPos; + + struct LoadedFont + { + hb_font_t* myHarfBuzzFont; + FT_Face freeTypeFace; + cairo_font_face_t* cairoFace; + + bool ready() + { return myHarfBuzzFont && freeTypeFace && cairoFace; } + + LoadedFont(): myHarfBuzzFont(nullptr), freeTypeFace(nullptr), cairoFace(nullptr) {} + }; + + static std::map fontCache; + + LoadedFont myLoadedFont; + + LoadedFont loadFace(DisplayItem* style); + + + bool validState; + + + +public: + RunWrapper(DisplayItem* style, const std::string text); + ~RunWrapper(); + + bool checkValidState() + { return validState; } + + Dimens getDimens() const + { return myDimens; } + + Coords getBearings() const + { return bearings; } + + //API inversion? + int getItemFlowDimension() const + { return getDimens().get(itemFlowAngle); } + + std::vector getCairoGlyphs() const; + + void prepareCairoContext(cairo_t* theCai, DisplayItem* theItem); +}; + +FT_Library RunWrapper::myFtLib = nullptr; +std::map RunWrapper::fontCache = + std::map(); + + +RunWrapper::RunWrapper(DisplayItem* style, const std::string text): + harfBuzzBuffer(nullptr), + glyphInfo(nullptr), + glyphPos(nullptr), + validState(false), + itemFlowAngle(Default::Filter::getItemFlowAngle(style)) +{ + if(!myFtLib) + { + auto error = FT_Init_FreeType(&myFtLib); + if(error) return; + } + + myLoadedFont = loadFace(style); + { + const int sizeInPt = Default::Filter::getFontSize(style); + + //Something something size in pt so "dpi" is 72 by definition? Seems to be the + // same as what cairo does. + const int hdpi = 72; + const int vdpi = 72; + + //Alright, so we have to set the size here, because cairo apparently caches + // the size on the font? Because if we don't do this, the next call to + // hb_shape with the same font but different size results in really weird spacing. + //The only difference being that the "cairo_set_font_size" has been called with + // the previous font, but not the current size. It *MUST* be caching it here + //Which, incidentally, means that this is exactly where we should be updating it + // anyway~ + FT_Set_Char_Size(myLoadedFont.freeTypeFace, 0, sizeInPt * 64, hdpi, vdpi); + } + + if(!myLoadedFont.ready()) return; + + harfBuzzBuffer = hb_buffer_create(); + hb_buffer_add_utf8(harfBuzzBuffer, text.c_str(), -1, 0, -1); + + hb_buffer_set_direction(harfBuzzBuffer, HB_DIRECTION_LTR); + hb_buffer_set_script(harfBuzzBuffer, HB_SCRIPT_LATIN); + hb_buffer_set_language(harfBuzzBuffer, hb_language_from_string(text.c_str(), -1)); + + hb_shape(myLoadedFont.myHarfBuzzFont, harfBuzzBuffer, nullptr, 0); + + glyphInfo = hb_buffer_get_glyph_infos(harfBuzzBuffer, &glyphCount); + glyphPos = hb_buffer_get_glyph_positions(harfBuzzBuffer, &glyphCount); + + + + { + //absolutely arbitrary size... We just need a valid surface to test against... TODO + cairo_surface_t* surf = createNewCairoSurfaceForPlatform(Dimens(100,100)); + cairo_t* myCai = cairo_create(surf); + + prepareCairoContext(myCai, style); + + auto glyphs = getCairoGlyphs(); + cairo_text_extents_t extents; + + cairo_glyph_extents(myCai, &glyphs[0], glyphs.size(), &extents); + + bearings.x = extents.x_bearing; + bearings.y = extents.y_bearing; + + const Angle rowFlowAngle = Default::Filter::getRowFlowAngle(style); + + myDimens.get(rowFlowAngle) = rowFlowAngle == Angle::Horizontal ? extents.width + : extents.height; + + //Gotta include that dank as whitespace. + myDimens.get(itemFlowAngle) = itemFlowAngle == Angle::Horizontal ? extents.x_advance + : extents.y_advance; + + cairo_destroy(myCai); + cairo_surface_destroy(surf); + } + + validState = true; +} + +RunWrapper::LoadedFont RunWrapper::loadFace(DisplayItem *style) +{ + Default::Filter::FontData fontData = Default::Filter::getFontFace(style); + + auto pos = fontCache.find(fontData); + + if(pos != fontCache.end()) + return pos->second; + + LoadedFont myLoaded; + + Default::Filter::FontData myFont = Default::Filter::getFontFace(style); + auto error = FT_New_Memory_Face(myFtLib, myFont.data, myFont.size, 0, &myLoaded.freeTypeFace); + + if(error == FT_Err_Unknown_File_Format) return LoadedFont(); + else if(error) return LoadedFont(); + + myLoaded.myHarfBuzzFont = hb_ft_font_create_referenced(myLoaded.freeTypeFace); + + if(error) return LoadedFont(); + + myLoaded.cairoFace = cairo_ft_font_face_create_for_ft_face(myLoaded.freeTypeFace, 0); + + fontCache[fontData] = myLoaded; + return myLoaded; +} + +RunWrapper::~RunWrapper() +{ + //Alright we'll just never fucking free anything, have it your way, cairo. + hb_buffer_destroy(harfBuzzBuffer); +} + +std::vector RunWrapper::getCairoGlyphs() const +{ + std::vector cairoGlyphs; + + int xAdvance = 0; + int yAdvance = 0; + + for(int i = 0; i != glyphCount; ++i) + { + cairo_glyph_t oneCairoGlyph; + + //According to the harfbuzz docs, hb_glyph_info_t::codepoint refers to a unicode codepoint + // before shaping, and to the glyph index after, which is what cairo needs here. + oneCairoGlyph.index = glyphInfo[i].codepoint; + hb_glyph_position_t hbGlyphPos = glyphPos[i]; + + oneCairoGlyph.x = xAdvance; + oneCairoGlyph.y = yAdvance; + + xAdvance += hbGlyphPos.x_advance / 64; + yAdvance += hbGlyphPos.y_advance / 64; + + cairoGlyphs.push_back(oneCairoGlyph); + } + + return cairoGlyphs; +} + +void RunWrapper::prepareCairoContext(cairo_t* theCai, DisplayItem* theItem) +{ + //TODO: Would "font height" be a good attribute? It's more controllable, I guess, and certainly + // more predictable. Apparently that's what font-size is in CSS (sum of ascent and descent) + cairo_set_font_face(theCai, myLoadedFont.cairoFace); + cairo_set_font_size(theCai, Default::Filter::getFontSize(theItem)); + assert(!cairo_status(theCai)); +} + + +Dimens getRunDimensions(DisplayItem* item, const std::string text) +{ + return RunWrapper(item, text).getDimens(); +} + +int getMaxStringLengthForSpace(DisplayItem* style, const std::string text, const int space) +{ + int lastGoodSize = 0; //'member yer last goooooooooood size? Aww yeh... + + for(; lastGoodSize != text.size(); ++lastGoodSize) + { + Text::RunWrapper myRun(style, text.substr(0, lastGoodSize)); + if(myRun.getItemFlowDimension() > space) + return lastGoodSize ? --lastGoodSize + : lastGoodSize; + } + return lastGoodSize; +} + +}//Text + + +void CairoCanvas::drawText(Coords origin, const std::string text, DisplayItem *style) +{ + Text::RunWrapper myRun = Text::RunWrapper(style, text); + auto cairoGlyphs = myRun.getCairoGlyphs(); + const Coords textPos = origin - myRun.getBearings(); + + drawWith(Default::Filter::getFontColor(style), Default::Filter::getAlpha(style), [&](cairo_t* cai) + { + myRun.prepareCairoContext(cai, style); + cairo_translate(cai, textPos.x, textPos.y); + //cairo_scale(cai, 1, 1); + cairo_show_glyphs(cai, &cairoGlyphs[0], cairoGlyphs.size()); + + auto i = cairo_status(cai); + assert(i == 0); + }); +} + +}//IVD diff --git a/src/standardstatekeys.h b/src/standardstatekeys.h new file mode 100755 index 0000000..2cfe564 --- /dev/null +++ b/src/standardstatekeys.h @@ -0,0 +1,18 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef STANDARDSTATEKEYS_H +#define STANDARDSTATEKEYS_H + +#include + +namespace IVD +{ +namespace StateKeys +{ + +static const ValueKeyPath triggerStateClicked = { "clicked" }; + +}//StateKeys +}//IVD + +#endif // STANDARDSTATEKEYS_H diff --git a/src/statekey.h b/src/statekey.h new file mode 100755 index 0000000..eb49cd4 --- /dev/null +++ b/src/statekey.h @@ -0,0 +1,35 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef IVD_STATEKEY_H +#define IVD_STATEKEY_H + +#include "rustutils/lexcompare.h" +#include "valuekey.h" + +namespace IVD +{ + +class DisplayItem; + +struct StateKey +{ + ValueKey identity; + void* scope; + + StateKey(): scope(nullptr) {} + StateKey(const ValueKey key): + identity(key), + scope(nullptr) + {} + + StateKey(const ValueKey theIdentity, void* theScope): + identity(theIdentity), + scope(theScope) + {} + + RUSTUTILS_DEFINE_COMP(StateKey, identity, scope) +}; + +}//IVD + +#endif // IVD_STATEKEY_H diff --git a/src/statemanager.cpp b/src/statemanager.cpp new file mode 100644 index 0000000..dc100fa --- /dev/null +++ b/src/statemanager.cpp @@ -0,0 +1,178 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "statemanager.h" + +namespace IVD +{ + +bool State::mutate(const bool setTo, const uint64_t theStamp) +{ + if(active == setTo) return false; + active = setTo; + stamp = theStamp; + for(auto pair : observers) updateSingleObserver(pair.first, pair.second); + syncAffectedVirtualStateKeys(); + return true; +} + +void State::syncAffectedVirtualStateKeys() +{ + for(auto vskey : affectedVirtualKeys) + vskey.syncProxyState(myStateManager); +} + +void State::insertVirtualStateKey(VirtualStateKey vskey) +{ + myVirtualStateKey = vskey; + myVirtualStateKey->syncProxyState(myStateManager); +} + +State& StateManager::initState(const StateKey key) +{ + if(key.scope) volatileStateMap[key.scope].push_back(key.identity); + State& theState = states[key.identity][key.scope]; + theState.ughSetStateManager(this); + return theState; +} + +State* StateManager::findState(const StateKey key) +{ + auto rangeIt = states.find(key.identity); + if(rangeIt == states.end()) return nullptr; + + StateRange& range = rangeIt->second; + auto scopeIt = range.find(key.scope); + if(scopeIt == range.end()) return nullptr; + + return &scopeIt->second; +} + +void StateManager::eraseState(const StateKey key) +{ + states[key.identity].erase(key.scope); +} + +void StateManager::eraseStateIfOrphaned(const StateKey key) +{ + auto rangeIterator = states.find(key.identity); + if(rangeIterator == states.end()) return; + + auto stateIt = rangeIterator->second.find(key.scope); + if(stateIt == rangeIterator->second.end()) return; + + State* state = &stateIt->second; + if(!state->checkObserved()) + { + if(stateIt->second.getVirtualStateKey()) + { + //Honest to god I want to know if this condition is ever met. + auto vskey = *stateIt->second.getVirtualStateKey(); + for(StateKey affectedStateKey : vskey.getAffectedKeys()) + { + State* affectedState = findState(affectedStateKey); + if(!affectedState) continue; + + affectedState->removeAffectedVirtualStateKey(vskey); + eraseStateIfOrphaned(affectedStateKey); + } + } + + rangeIterator->second.erase(stateIt); + + //Erase range if orphaned + if(rangeIterator->second.empty()) + states.erase(rangeIterator); + } +} + +void StateManager::deallocateScope(void* scope) +{ + for(ValueKey key : volatileStateMap[scope]) + eraseStateIfOrphaned(StateKey(key, scope)); + + volatileStateMap.erase(scope); +} + +void StateManager::attemptMutate(State* theState, const bool flag) +{ + uint64_t tryStamp = lastStamp + 1; + + if(theState->mutate(flag, tryStamp)) + { + lastStamp = tryStamp; //Commit + } +} + +void StateManager::removeReferencesToDisplayItem(DisplayItem* item) +{ + //Remove any states on the item. + deallocateScope(item); + + //Remove item from any states it observes. + for(StateKey key : displayItemObserverMap[item]) + { + State* state = findState(key); + findState(key)->removeObserver(item); + + eraseStateIfOrphaned(key); + } + + displayItemObserverMap.erase(item); +} + +void StateManager::insertVirtualState(VirtualStateKey vskey) +{ + State* proxyState = findState(vskey.proxyStateKey); + assert(proxyState); //I mean it could be a no-op otherwise, but why? + + proxyState->insertVirtualStateKey(vskey); + + for(StateKey key : vskey.getAffectedKeys()) + { + State* affectedState = findState(key); + + //If the affected state doesn't exist, it means that this hasn't been run + // and or the state is not observed directly by a DisplayItem. But it could + // still be mutated and virtual keys count as observers. + if(!affectedState) affectedState = &initState(key); + + affectedState->registerAffectedVirtualStateKey(vskey); + } +} + +bool StateManager::checkAny(const StateKey key) +{ + for(auto range : states.at(key.identity)) + { + if(range.second.check()) + return true; + } + return false; +} + +bool StateManager::mutateIfObserved(const StateKey key, const bool active) +{ + auto state = findState(key); + if(!state) return false; + + assert(state->checkObserved()); + + attemptMutate(state, active); + return true; +} + +void StateManager::mutateAll(const StateKey key, const bool active) +{ + for(auto& pair : states[key.identity]) + { + void* scope = pair.first; + State& state = pair.second; + + //Ignore the global, it's semantically independent and inherently singular + if(!scope) continue; + + attemptMutate(&state, active); + } +} + +}//IVD diff --git a/src/statemanager.h b/src/statemanager.h new file mode 100755 index 0000000..22d4df5 --- /dev/null +++ b/src/statemanager.h @@ -0,0 +1,156 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef STATEMANAGER_H +#define STATEMANAGER_H + +#include + +#include "assert.h" + +#include "statekey.h" +#include "virtualstatekey.h" +#include "displayitem.h" +#include "element.h" + +namespace IVD +{ + +class StateManager; + +class State +{ + //The only important thing a state does is associate a + // set of attributes to the current system state. + //So why not store the attribute set along with the + // observer of this state, and tell it when + // they've changed. + bool active; + uint64_t stamp; + std::map observers; + StateManager* myStateManager; + + std::optional myVirtualStateKey; + std::set affectedVirtualKeys; + + void updateSingleObserver(DisplayItem* item, AttributePositionPair pair) + { + if(active) item->addAttributeSet(pair); + else item->removeAttributeSet(pair); + } + +public: + State(): active(false), stamp(0) {} + + void ughSetStateManager(StateManager* statemanager) + { myStateManager = statemanager; } + + void reigsterObserver(DisplayItem* item, AttributePositionPair pair) + { + observers[item] = pair; + updateSingleObserver(item, pair); + } + + int checkObserved() { return observers.size() || affectedVirtualKeys.size(); } + void removeObserver(DisplayItem* item) { observers.erase(item); } + + bool mutate(const bool setTo, const uint64_t theStamp); + + void syncAffectedVirtualStateKeys(); + void insertVirtualStateKey(VirtualStateKey vskey); + + void registerAffectedVirtualStateKey(VirtualStateKey key) + { affectedVirtualKeys.insert(key); } + + void removeAffectedVirtualStateKey(VirtualStateKey vskey) + { affectedVirtualKeys.erase(vskey); } + + std::optional getVirtualStateKey() + { return myVirtualStateKey; } + + bool check() { return active; } + uint64_t getStamp() { return stamp; } +}; + +class StateManager +{ + //First stamp is always 1. Unstamped states are == 0. + uint64_t lastStamp; + //What about compound states... + typedef std::map StateRange; + std::map states; + + std::vector triggerStates; + std::map> volatileStateMap; + std::map> displayItemObserverMap; + + State& initState(const StateKey key); + State* findState(const StateKey key); + void eraseState(const StateKey key); + + void eraseStateIfOrphaned(const StateKey key); + void deallocateScope(void* scope); + void attemptMutate(State* theState, const bool flag); + +public: + StateManager(): lastStamp(0) {} + + void removeReferencesToDisplayItem(DisplayItem* item); + //void removeReferencesToModel(ModelItemBase* scope) + //{ deallocateScope(scope); } + + void registerStateObserver(const StateKey key, + DisplayItem* observer, + AttributePositionPair pair) + { + displayItemObserverMap[observer].push_back(key); + initState(key).reigsterObserver(observer, pair); + } + + void insertVirtualState(VirtualStateKey vskey); + + bool checkState(const StateKey key) + { + State* state = findState(key); + if(!state) return false; + return state->check(); + } + + bool checkAny(const StateKey key); + + uint64_t getStamp(const StateKey key) + { + State* state = findState(key); + if(!state) return 0; + return state->getStamp(); + } + + uint64_t getLastStamp() + { return lastStamp; } + + //False just means it's not observed, not that it mutated, specifically. + //Kinda shoddy... The only thing that needs this is setTriggerIfObserved... + bool mutateIfObserved(const StateKey key, const bool active); + + void mutateAll(const StateKey key, const bool active); + + void setTriggerIfObserved(const StateKey key) + { + if(mutateIfObserved(key, true)) + triggerStates.push_back(key); + } + + void resetTriggerStates() + { + for(StateKey key : triggerStates) + attemptMutate(findState(key), false); + + triggerStates.clear(); + } + + bool hasTriggerStates() + { return triggerStates.size(); } +}; + +}//IVD + +#endif // STATEMANAGER_H diff --git a/src/states.h b/src/states.h new file mode 100644 index 0000000..031fc07 --- /dev/null +++ b/src/states.h @@ -0,0 +1,272 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef STATES_H +#define STATES_H + +#include + +namespace IVD +{ + +namespace States +{ + +typedef std::string Symbol; + +enum class StateType +{ + Press, + Release, + Active, +}; + +enum class KeyType +{ + Positional, + Literal, +}; + +//Just for consistency, to pretend that the below +// are just generic constants. WHICH YOU SHOULD!!! +inline Symbol getButton(const Symbol button) +{ return button; } + +inline Symbol getButtonActive(const Symbol button) +{ return (std::string(button) + "-Active").c_str(); } + +inline Symbol getButtonPress(const Symbol button) +{ return (std::string(button) + "-Press").c_str(); } + +inline Symbol getButtonRelease(const Symbol button) +{ return (std::string(button) + "-Release").c_str(); } + + +namespace Item +{ + +const Symbol HoverExclusive = "IVD-Item-Hover"; +const Symbol HoverInclusive = "IVD-Item-Hover-Inclusive"; + +//const Symbol GeometryChanged = "IVD-Item-Geometry-Changed"; + +const Symbol ModelChanged = "Model-Changed"; + +}//Item + +namespace Window +{ +/** \brief */ + +//These are only set on the display item. +/** \brief State: Set while the window is visible. Item specific.*/ +const Symbol Visible = "IVD-Window-Visible"; + +/** \brief State: Set while the window is hidden, Item specific.*/ +const Symbol Hidden = "IVD-Window-Hidden"; + +/** \brief Trigger State: Set when the window is shown. Item specific.*/ +const Symbol Shown = "IVD-Window-Shown"; + +/** \brief Trigger State: This is set after a window is initialized, + * and the geometry is set for the first time. Item specific.*/ +const Symbol Initialized = "IVD-Window-Initialized"; + +//Is there a difference between mouse and keyboard focus? +//(mouse focus just means the mouse is over it?) + +/** \brief State: Set while the window has keyboard focus. Item Specific.*/ +const Symbol Focused = "IVD-Window-Focused"; + +/** \brief State: Set while the window has mouse focus. Item Specific.*/ +const Symbol MouseFocus = "IVD-Window-Focus-Mouse"; + +/** \brief Trigger State: Set when the window is minimized. Item Specific.*/ +const Symbol Minimized = "IVD-Window-Minimized"; + +/** \brief Trigger State: Set when the window is maximized. Item Specific.*/ +const Symbol Maximized = "IVD-Window-Maximized"; + +/** \brief Trigger State: Set when the close button is pressed on a window. Item Specific.*/ +const Symbol CloseRequest = "IVD-Window-Close"; + +}//Window + +namespace App +{ +/** \brief Trigger State: Set when the application receives a close event, + * in addition to IVD-Window-Close for the last window.*/ +const Symbol CloseApp = "IVD-Close"; +}//App + +namespace Key +{ +//Keyboards are more standard than you'd expect, +// but perhaps not as standard as they should you'd hope be wise. + +//Which one is ze any key? +const Symbol Any = "IVD-Any-Key"; + +const Symbol Play = "IVD-Key-Media-Play"; +const Symbol Stop = "IVD-Key-Media-Stop"; +const Symbol Previous = "IVD-Key-Media-Previous"; +const Symbol Next = "IVD-Key-Media-Next"; + +const Symbol LeftSuper = "IVD-Key-Left-Super"; +const Symbol LeftShift = "IVD-Key-Left-Shift"; +const Symbol LeftCtrl = "IVD-Key-Left-Ctrl"; +const Symbol LeftAlt = "IVD-Key-Left-Alt"; + +const Symbol RightSuper = "IVD-Key-Right-Super"; +const Symbol RightShift = "IVD-Key-Right-Shift"; +const Symbol RightCtrl = "IVD-Key-Right-Ctrl"; +const Symbol RightAlt = "IVD-Key-Right-Alt"; + +const Symbol CapsLock = "IVD-Key-Caps-Lock"; +const Symbol AltGr = "IVD-Key-Alt-Gr"; +const Symbol Menu = "IVD-Key-Menu"; + +const Symbol PrintScreen = "IVD-Key-Print-Screen"; +const Symbol ScrollLock = "IVD-Key-Scroll-Lock"; +const Symbol Pause = "IVD-Key-Pause"; + +const Symbol Insert = "IVD-Key-Insert"; +const Symbol Delete = "IVD-Key-Delete"; +const Symbol Home = "IVD-Key-Home"; +const Symbol End = "IVD-Key-End"; +const Symbol PageUp = "IVD-Key-Page-Up"; +const Symbol PageDown = "IVD-Key-Page-Down"; + +const Symbol LeftArrow = "IVD-Key-Arrow-Left"; +const Symbol RightArrow = "IVD-Key-Arrow-Right"; +const Symbol UpArrow = "IVD-Key-Arrow-Up"; +const Symbol DownArrow = "IVD-Key-Arrow-Down"; + +const Symbol NumLock = "IVD-Key-Num-Lock"; + +const Symbol Escape = "IVD-Key-Escape"; + + +const Symbol F1 = "IVD-Key-F1"; +const Symbol F2 = "IVD-Key-F2"; +const Symbol F3 = "IVD-Key-F3"; +const Symbol F4 = "IVD-Key-F4"; +const Symbol F5 = "IVD-Key-F5"; +const Symbol F6 = "IVD-Key-F6"; +const Symbol F7 = "IVD-Key-F7"; +const Symbol F8 = "IVD-Key-F8"; +const Symbol F9 = "IVD-Key-F9"; +const Symbol F10 = "IVD-Key-F10"; +const Symbol F11 = "IVD-Key-F11"; +const Symbol F12 = "IVD-Key-F12"; + +//Row 0 +const Symbol Tilde = "IVD-Scan-Tilde"; +const Symbol RowNum1 = "IVD-Scan-Row-Num-1"; +const Symbol RowNum2 = "IVD-Scan-Row-Num-2"; +const Symbol RowNum3 = "IVD-Scan-Row-Num-3"; +const Symbol RowNum4 = "IVD-Scan-Row-Num-4"; +const Symbol RowNum5 = "IVD-Scan-Row-Num-5"; +const Symbol RowNum6 = "IVD-Scan-Row-Num-6"; +const Symbol RowNum7 = "IVD-Scan-Row-Num-7"; +const Symbol RowNum8 = "IVD-Scan-Row-Num-8"; +const Symbol RowNum9 = "IVD-Scan-Row-Num-9"; +const Symbol RowNum0 = "IVD-Scan-Row-Num-0"; +const Symbol Minus = "IVD-Scan-Minus"; +const Symbol Equals = "IVD-Scan-Equals"; +const Symbol Backspace = "IVD-Scan-Backspace"; + +//Row 1 +const Symbol Tab = "IVD-Scan-Tab"; +const Symbol Q = "IVD-Scan-Q"; +const Symbol W = "IVD-Scan-W"; +const Symbol E = "IVD-Scan-E"; +const Symbol R = "IVD-Scan-R"; +const Symbol T = "IVD-Scan-T"; +const Symbol Y = "IVD-Scan-Y"; +const Symbol U = "IVD-Scan-U"; +const Symbol I = "IVD-Scan-I"; +const Symbol O = "IVD-Scan-O"; +const Symbol P = "IVD-Scan-P"; +const Symbol LeftBracket = "IVD-Scan-Left-Bracket"; +const Symbol RightBracket = "IVD-Scan-Right-Bracket"; +const Symbol Backslash = "IVD-Scan-Backslash"; + +//Row 2 +const Symbol A = "IVD-Scan-A"; +const Symbol S = "IVD-Scan-S"; +const Symbol D = "IVD-Scan-D"; +const Symbol F = "IVD-Scan-F"; +const Symbol G = "IVD-Scan-G"; +const Symbol H = "IVD-Scan-H"; +const Symbol J = "IVD-Scan-J"; +const Symbol K = "IVD-Scan-K"; +const Symbol L = "IVD-Scan-L"; +const Symbol Semicolon = "IVD-Scan-Semicolon"; +const Symbol Apostrophe = "IVD-Scan-Apostrophe"; +const Symbol Return = "IVD-Scan-Return"; + +//Row 3 +const Symbol Z = "IVD-Scan-Z"; +const Symbol X = "IVD-Scan-X"; +const Symbol C = "IVD-Scan-C"; +const Symbol V = "IVD-Scan-V"; +const Symbol B = "IVD-Scan-B"; +const Symbol N = "IVD-Scan-N"; +const Symbol M = "IVD-Scan-M"; +const Symbol Comma = "IVD-Scan-Comma"; +const Symbol Period = "IVD-Scan-Period"; +const Symbol Slash = "IVD-Scan-Slash"; + +//Row 4 +const Symbol Spacebar = "IVD-Scan-Spacebar"; + +namespace Pad +{ + +//Just going to start with basics +const Symbol Slash = "IVD-Scan-Pad-Slash"; +const Symbol Star = "IVD-Scan-Pad-Star"; +const Symbol Minus = "IVD-Scan-Pad-Minus"; + +const Symbol Num7 = "IVD-Scan-Pad-7"; +const Symbol Num8 = "IVD-Scan-Pad-8"; +const Symbol Num9 = "IVD-Scan-Pad-9"; +const Symbol Plus = "IVD-Scan-Pad-Plus"; + +const Symbol Num4 = "IVD-Scan-Pad-4"; +const Symbol Num5 = "IVD-Scan-Pad-5"; +const Symbol Num6 = "IVD-Scan-Pad-6"; +const Symbol Tab = "IVD-Scan-Pad-Tab"; + +const Symbol Num1 = "IVD-Scan-Pad-1"; +const Symbol Num2 = "IVD-Scan-Pad-2"; +const Symbol Num3 = "IVD-Scan-Pad-3"; + +const Symbol Num0 = "IVD-Scan-Pad-0"; +const Symbol Period = "IVD-Scan-Pad-Period"; + +const Symbol Return = "IVD-Scan-Pad-Return"; + +}//Pad +}//Key + + +namespace Mouse +{ + +const Symbol ButtonLeft = "IVD-Mouse-Left"; +const Symbol ButtonMiddle = "IVD-Mouse-Middle"; +const Symbol ButtonRight = "IVD-Mouse-Right"; +const Symbol ButtonFour = "IVD-Mouse-Four"; +const Symbol ButtonFive = "IVD-Mouse-Five"; + +const Symbol Motion = "IVD-Mouse-Motion"; +const Symbol Wheel = "IVD-Mouse-Wheel"; + +}//Mouse + + +}//States +}//IVD + +#endif // STATES_H diff --git a/src/tests/COMPILE-ERROR.IVD b/src/tests/COMPILE-ERROR.IVD new file mode 100644 index 0000000..f5860d8 --- /dev/null +++ b/src/tests/COMPILE-ERROR.IVD @@ -0,0 +1,61 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + width: 640; + height: 400; + + layout: stack; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#main-content-container -> content: +{ + position-within: window; + layout: hbox; + +} + +//#welcome; + +#sidebar +{ + position-within: main-content-container; + layout: vbox; +} + +#menu-item -> menu-items +{ + position-within: sidebar; + text: model.menu-title; + + padding: 10; + + trans-x: -width; + + induce-state: animating; + +state animating: + trans-x: 0, ease-in(1000ms, graph(linear, 0 @ 0, 1 @ 1)); + + +state IVD-Item-Hover & ::.IVD-Mouse-Left-Press: + trigger: model.update-content; + +state IVD-Item-Hover: + color: purple; +} + +#content -> content +{ + position-within: main-content-container; + text: model.content; +} diff --git a/src/tests/accept.sh b/src/tests/accept.sh new file mode 100755 index 0000000..d01d97b --- /dev/null +++ b/src/tests/accept.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +#This file is part of the IVD project and is licensed under LGPL-3.0-only + +compiler="./ivdserializingcompiler" + +for arg in "$@" +do + dir=$(dirname $arg) + outdir=$dir/ivdserial + mkdir -p $outdir; + + name=$(basename $arg) + + newPath=$outdir/$name"serial" + + $compiler $arg > $newPath +done diff --git a/src/tests/catch2testmain.cpp b/src/tests/catch2testmain.cpp new file mode 100644 index 0000000..1ce3870 --- /dev/null +++ b/src/tests/catch2testmain.cpp @@ -0,0 +1,4 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#define CATCH_CONFIG_MAIN +#include diff --git a/src/tests/errors/badAttrkey.ivd b/src/tests/errors/badAttrkey.ivd new file mode 100755 index 0000000..d3ed27f --- /dev/null +++ b/src/tests/errors/badAttrkey.ivd @@ -0,0 +1,9 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + fjkd: rie; + width: 1004; +} diff --git a/src/tests/errors/badPropertyBody.ivd b/src/tests/errors/badPropertyBody.ivd new file mode 100755 index 0000000..90ecfef --- /dev/null +++ b/src/tests/errors/badPropertyBody.ivd @@ -0,0 +1,8 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + align-x: center; +} diff --git a/src/tests/errors/badUnnaturals.ivd b/src/tests/errors/badUnnaturals.ivd new file mode 100755 index 0000000..4399f2d --- /dev/null +++ b/src/tests/errors/badUnnaturals.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#elem +{ + margin: 42, 34, 7; //Wrong number + margin: ease-in(100ms, graph(linear, 0 @ 1)); //No no + margin: delay(100ms); //NUH! +} diff --git a/src/tests/errors/badlist.ivd b/src/tests/errors/badlist.ivd new file mode 100644 index 0000000..2ae583e --- /dev/null +++ b/src/tests/errors/badlist.ivd @@ -0,0 +1,9 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + cell-names: jkfk ajj akakk; + bind-state: fjjfj ajja ak; +} diff --git a/src/tests/errors/badsetanddeclarescope.ivd b/src/tests/errors/badsetanddeclarescope.ivd new file mode 100755 index 0000000..6dcb72b --- /dev/null +++ b/src/tests/errors/badsetanddeclarescope.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + set: width = 4; +state s: + declare expression: f = width * 4; +} diff --git a/src/tests/errors/badspec.ivd b/src/tests/errors/badspec.ivd new file mode 100644 index 0000000..d43cbb0 --- /dev/null +++ b/src/tests/errors/badspec.ivd @@ -0,0 +1,5 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#{}; +fsdjklfhas;jklcn +anhvfjk'nv'an fghjk; adfjkgn' wHEEEEEE diff --git a/src/tests/errors/expectingbodyhandlers.ivd b/src/tests/errors/expectingbodyhandlers.ivd new file mode 100644 index 0000000..a55456e --- /dev/null +++ b/src/tests/errors/expectingbodyhandlers.ivd @@ -0,0 +1,17 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + layout: ...; + position-within: ...; + color: ...; + induce-state: ...; + text: ...; + height: ...; + cell-names: ...; + font: ...; + align-x: ...; + margin: ...; +} diff --git a/src/tests/errors/ivdserial/badAttrkey.ivdserial b/src/tests/errors/ivdserial/badAttrkey.ivdserial new file mode 100644 index 0000000..372d34b --- /dev/null +++ b/src/tests/errors/ivdserial/badAttrkey.ivdserial @@ -0,0 +1,16 @@ +The IVD compiler has encountered the following error: +Syntax error on line 19: + fjkd: rie; + ^ +Token is not a state or variable modifier or attribute key. + +--------------------------------------------------- +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: size-a +Main Constraint + | +1004.000000i + diff --git a/src/tests/errors/ivdserial/badPropertyBody.ivdserial b/src/tests/errors/ivdserial/badPropertyBody.ivdserial new file mode 100644 index 0000000..05dd9ca --- /dev/null +++ b/src/tests/errors/ivdserial/badPropertyBody.ivdserial @@ -0,0 +1,11 @@ +The IVD compiler has encountered the following error: +Syntax error on line 19: + align-x: center; + ^ +Malformed attribute body, expected Property. + +--------------------------------------------------- +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/badUnnaturals.ivdserial b/src/tests/errors/ivdserial/badUnnaturals.ivdserial new file mode 100644 index 0000000..a42f2e7 --- /dev/null +++ b/src/tests/errors/ivdserial/badUnnaturals.ivdserial @@ -0,0 +1,21 @@ +The IVD compiler has encountered the following errors +Syntax error on line 19: + margin: 42, 34, 7; //Wrong number + ^ +Invalid number of arguments to unatural key margin, must be 1 or 4. + +Syntax error on line 20: + margin: ease-in(100ms, graph(linear, 0 @ 1)); //No no + ^ +Unnatural key cannot contain ease specifiers. + +Syntax error on line 21: + margin: delay(100ms); //NUH! + ^ +Unnatural key cannot contain delay specifiers. + +--------------------------------------------------- +=================================Element======================================== +Path: elem +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/badlist.ivdserial b/src/tests/errors/ivdserial/badlist.ivdserial new file mode 100644 index 0000000..328479c --- /dev/null +++ b/src/tests/errors/ivdserial/badlist.ivdserial @@ -0,0 +1,16 @@ +The IVD compiler has encountered the following errors +Syntax error on line 19: + cell-names: jkfk ajj akakk; + ^ +Was expecting: ";", got: "User Token" + +Syntax error on line 20: + bind-state: fjjfj ajja ak; + ^ +Was expecting: ";", got: "User Token" + +--------------------------------------------------- +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/badsetanddeclarescope.ivdserial b/src/tests/errors/ivdserial/badsetanddeclarescope.ivdserial new file mode 100644 index 0000000..cf104b3 --- /dev/null +++ b/src/tests/errors/ivdserial/badsetanddeclarescope.ivdserial @@ -0,0 +1,17 @@ +The IVD compiler has encountered the following errors +Syntax error on line 19: + set: width = 4; + ^ +Set not allowed in default state. + +Syntax error on line 21: + declare expression: f = width * 4; + ^ +Declare not allowed in non-default state. + +--------------------------------------------------- +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +-----------------------------State Key: this.s diff --git a/src/tests/errors/ivdserial/badspec.ivdserial b/src/tests/errors/ivdserial/badspec.ivdserial new file mode 100644 index 0000000..3a104c5 --- /dev/null +++ b/src/tests/errors/ivdserial/badspec.ivdserial @@ -0,0 +1,7 @@ +The IVD compiler has encountered the following error: +Syntax error on line 15: +#{}; +^ +Expected Spec declaration. + +--------------------------------------------------- diff --git a/src/tests/errors/ivdserial/expectingbodyhandlers.ivdserial b/src/tests/errors/ivdserial/expectingbodyhandlers.ivdserial new file mode 100644 index 0000000..a883b62 --- /dev/null +++ b/src/tests/errors/ivdserial/expectingbodyhandlers.ivdserial @@ -0,0 +1,56 @@ +The IVD compiler has encountered the following errors +Syntax error on line 19: + layout: ...; + ^ +Malformed attribute body, expected User Token, or Property. + +Syntax error on line 20: + position-within: ...; + ^ +Was expecting: "User Token", got: "." + +Syntax error on line 21: + color: ...; + ^ +Malformed attribute body, expected Color Literal. + +Syntax error on line 22: + induce-state: ...; + ^ +Was expecting: "User Token", got: "." + +Syntax error on line 23: + text: ...; + ^ +Invalid value for key, expected user token or attribute key. + +Syntax error on line 24: + height: ...; + ^ +Malformed Expression, expected Scalar or Value Key. + +Syntax error on line 25: + cell-names: ...; + ^ +Malformed attribute body, expected User Token List. + +Syntax error on line 26: + font: ...; + ^ +Malformed attribute body, expected String Literal, or Property. + +Syntax error on line 27: + align-x: ...; + ^ +Malformed attribute body, expected Property. + +Syntax error on line 28: + margin: ...; + ^ +Malformed Expression, expected Scalar or Value Key. + +--------------------------------------------------- +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/malformedAttrBodyRecovery.ivdserial b/src/tests/errors/ivdserial/malformedAttrBodyRecovery.ivdserial new file mode 100644 index 0000000..a97a22e --- /dev/null +++ b/src/tests/errors/ivdserial/malformedAttrBodyRecovery.ivdserial @@ -0,0 +1,16 @@ +The IVD compiler has encountered the following error: +Syntax error on line 19: + height: 1004.; + ^ +Malformed Expression, expected Scalar or Value Key. + +--------------------------------------------------- +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: size-a +Main Constraint + | +100.000000i + diff --git a/src/tests/errors/ivdserial/malformedclassheader.ivdserial b/src/tests/errors/ivdserial/malformedclassheader.ivdserial new file mode 100644 index 0000000..a8c6c92 --- /dev/null +++ b/src/tests/errors/ivdserial/malformedclassheader.ivdserial @@ -0,0 +1,20 @@ +The IVD compiler has encountered the following errors +Syntax error on line 17: +# : .class1, .class2; + ^ +Was expecting: "User Token", got: "." + +Syntax error on line 18: +# : .class2, .class1; + ^ +Was expecting: "User Token", got: "." + +--------------------------------------------------- +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/modelinunenumeratedelement.ivdserial b/src/tests/errors/ivdserial/modelinunenumeratedelement.ivdserial new file mode 100644 index 0000000..a394920 --- /dev/null +++ b/src/tests/errors/ivdserial/modelinunenumeratedelement.ivdserial @@ -0,0 +1,16 @@ +The IVD compiler has encountered the following error: +Syntax error on line 19: + width: model.widthat; //shit shit shit models can have this names fuck fuck + ^ +Model scoped value key in non-enumerated element. + +--------------------------------------------------- +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: size-a +Main Constraint + | +model.widthat + diff --git a/src/tests/errors/ivdserial/nameconflict.ivdserial b/src/tests/errors/ivdserial/nameconflict.ivdserial new file mode 100644 index 0000000..7f63758 --- /dev/null +++ b/src/tests/errors/ivdserial/nameconflict.ivdserial @@ -0,0 +1,20 @@ +The IVD compiler has encountered the following errors +Syntax error on line 18: +.class; +^ +Class name conflicts with previous definition on line: 17: +.class; +^ + +Syntax error on line 21: +#element; +^ +Element name conflicts with previous definition on line: 20: +#element; +^ + +--------------------------------------------------- +=================================Element======================================== +Path: element +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/property.ivdserial b/src/tests/errors/ivdserial/property.ivdserial new file mode 100644 index 0000000..3d348c6 --- /dev/null +++ b/src/tests/errors/ivdserial/property.ivdserial @@ -0,0 +1,11 @@ +The IVD compiler has encountered the following error: +Syntax error on line 3: + align-horizontal: center; + ^ +Malformed attribute body, expected Property. + +--------------------------------------------------- +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/propertyError.ivdserial b/src/tests/errors/ivdserial/propertyError.ivdserial new file mode 100644 index 0000000..abe7fcc --- /dev/null +++ b/src/tests/errors/ivdserial/propertyError.ivdserial @@ -0,0 +1,25 @@ +The IVD compiler has encountered the following errors +Syntax error on line 19: + align-x: outer; + ^ +Malformed attribute body, expected Property. + +Syntax error on line 20: + justify: center; + ^ +Malformed attribute body, expected Property. + +Syntax error on line 25: + align-y: center; + ^ +Malformed attribute body, expected Property. + +--------------------------------------------------- +=================================Element======================================== +Path: i +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: text1 +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/recoverAfterBadUnnaturalNumber.ivdserial b/src/tests/errors/ivdserial/recoverAfterBadUnnaturalNumber.ivdserial new file mode 100644 index 0000000..8ce7625 --- /dev/null +++ b/src/tests/errors/ivdserial/recoverAfterBadUnnaturalNumber.ivdserial @@ -0,0 +1,16 @@ +The IVD compiler has encountered the following errors +Syntax error on line 19: + margin: 42, 34, 7; + ^ +Invalid number of arguments to unatural key margin, must be 1 or 4. + +Syntax error on line 20: + margin: delay(100ms); + ^ +Unnatural key cannot contain delay specifiers. + +--------------------------------------------------- +=================================Element======================================== +Path: elem +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/remoracyclic.ivdserial b/src/tests/errors/ivdserial/remoracyclic.ivdserial new file mode 100644 index 0000000..1254bd7 --- /dev/null +++ b/src/tests/errors/ivdserial/remoracyclic.ivdserial @@ -0,0 +1,11 @@ +The IVD compiler has encountered the following error: +Syntax error on line 19: +@top + ^ +Remora host class already finalized. + +--------------------------------------------------- +=================================Element======================================== +Path: topInstance +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/remoradeepEnumerationMismatch.ivdserial b/src/tests/errors/ivdserial/remoradeepEnumerationMismatch.ivdserial new file mode 100644 index 0000000..f3087e8 --- /dev/null +++ b/src/tests/errors/ivdserial/remoradeepEnumerationMismatch.ivdserial @@ -0,0 +1,25 @@ +The IVD compiler has encountered the following errors +Syntax error on line 21: +@host : host1; + ^ +Remorial instance does not declare a model. + +Syntax error on line 18: +@host1 -> @::ayee; + ^ +But remora requires one. + +--------------------------------------------------- +=================================Element======================================== +Path: instance +Model x +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::anonymous-3-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::anonymous-3-HORRIBLY-MAANGLED::anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/remoraenumerationmismatch.ivdserial b/src/tests/errors/ivdserial/remoraenumerationmismatch.ivdserial new file mode 100644 index 0000000..14dcd42 --- /dev/null +++ b/src/tests/errors/ivdserial/remoraenumerationmismatch.ivdserial @@ -0,0 +1,20 @@ +The IVD compiler has encountered the following errors +Syntax error on line 19: +#instance : host; +^ +Remorial instance does not declare a model. + +Syntax error on line 18: +@host -> @::ayee; + ^ +But remora requires one. + +--------------------------------------------------- +=================================Element======================================== +Path: instance +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/remorafinalizedclass.ivdserial b/src/tests/errors/ivdserial/remorafinalizedclass.ivdserial new file mode 100644 index 0000000..cbb5739 --- /dev/null +++ b/src/tests/errors/ivdserial/remorafinalizedclass.ivdserial @@ -0,0 +1,11 @@ +The IVD compiler has encountered the following error: +Syntax error on line 24: +@top + ^ +Remora host class already finalized. + +--------------------------------------------------- +=================================Element======================================== +Path: topInstance +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/ivdserial/remoraundefinedclass.ivdserial b/src/tests/errors/ivdserial/remoraundefinedclass.ivdserial new file mode 100644 index 0000000..725a69d --- /dev/null +++ b/src/tests/errors/ivdserial/remoraundefinedclass.ivdserial @@ -0,0 +1,7 @@ +The IVD compiler has encountered the following error: +Syntax error on line 19: +@level4.remora : level3; + ^ +Remora host class undefined. + +--------------------------------------------------- diff --git a/src/tests/errors/ivdserial/tabserrorhandler.ivdserial b/src/tests/errors/ivdserial/tabserrorhandler.ivdserial new file mode 100644 index 0000000..05dd9ca --- /dev/null +++ b/src/tests/errors/ivdserial/tabserrorhandler.ivdserial @@ -0,0 +1,11 @@ +The IVD compiler has encountered the following error: +Syntax error on line 19: + align-x: center; + ^ +Malformed attribute body, expected Property. + +--------------------------------------------------- +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/errors/malformedAttrBodyRecovery.ivd b/src/tests/errors/malformedAttrBodyRecovery.ivd new file mode 100755 index 0000000..c9eed7d --- /dev/null +++ b/src/tests/errors/malformedAttrBodyRecovery.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + height: 1004.; + width: 100; + +} diff --git a/src/tests/errors/malformedclassheader.ivd b/src/tests/errors/malformedclassheader.ivd new file mode 100644 index 0000000..5448ae7 --- /dev/null +++ b/src/tests/errors/malformedclassheader.ivd @@ -0,0 +1,6 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# : .class1, .class2; +# : .class2, .class1; diff --git a/src/tests/errors/modelinunenumeratedelement.ivd b/src/tests/errors/modelinunenumeratedelement.ivd new file mode 100755 index 0000000..fa54f4a --- /dev/null +++ b/src/tests/errors/modelinunenumeratedelement.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.a1class1 +{ + width: model.widthat; //shit shit shit models can have this names fuck fuck +} + +# : a1class1; diff --git a/src/tests/errors/nameconflict.ivd b/src/tests/errors/nameconflict.ivd new file mode 100644 index 0000000..0cc14cd --- /dev/null +++ b/src/tests/errors/nameconflict.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.class; +.class; + +#element; +#element; + diff --git a/src/tests/errors/propertyError.ivd b/src/tests/errors/propertyError.ivd new file mode 100644 index 0000000..e899b4f --- /dev/null +++ b/src/tests/errors/propertyError.ivd @@ -0,0 +1,15 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#i +{ + align-x: outer; + justify: center; +} + +#text1 +{ + align-y: center; + +} diff --git a/src/tests/errors/recoverAfterBadUnnaturalNumber.ivd b/src/tests/errors/recoverAfterBadUnnaturalNumber.ivd new file mode 100755 index 0000000..2d8ba3c --- /dev/null +++ b/src/tests/errors/recoverAfterBadUnnaturalNumber.ivd @@ -0,0 +1,9 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#elem +{ + margin: 42, 34, 7; + margin: delay(100ms); +} diff --git a/src/tests/errors/remoracyclic.ivd b/src/tests/errors/remoracyclic.ivd new file mode 100755 index 0000000..64768d5 --- /dev/null +++ b/src/tests/errors/remoracyclic.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.top; + +@top +.remora : top; + +#topInstance : top; diff --git a/src/tests/errors/remoradeepEnumerationMismatch.ivd b/src/tests/errors/remoradeepEnumerationMismatch.ivd new file mode 100644 index 0000000..27b2b5d --- /dev/null +++ b/src/tests/errors/remoradeepEnumerationMismatch.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.host1; +@host1 -> @::ayee; + +.host; +@host : host1; +#instance -> x : host; diff --git a/src/tests/errors/remoraenumerationmismatch.ivd b/src/tests/errors/remoraenumerationmismatch.ivd new file mode 100644 index 0000000..704593d --- /dev/null +++ b/src/tests/errors/remoraenumerationmismatch.ivd @@ -0,0 +1,7 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.host; +@host -> @::ayee; +#instance : host; diff --git a/src/tests/errors/remorafinalizedclass.ivd b/src/tests/errors/remorafinalizedclass.ivd new file mode 100755 index 0000000..cb5058e --- /dev/null +++ b/src/tests/errors/remorafinalizedclass.ivd @@ -0,0 +1,15 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.top; + +.child; + +@child +.remora2 : top; + +@top +.remora : child; + +#topInstance : top; diff --git a/src/tests/errors/remoraundefinedclass.ivd b/src/tests/errors/remoraundefinedclass.ivd new file mode 100755 index 0000000..27c3d9e --- /dev/null +++ b/src/tests/errors/remoraundefinedclass.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.level3; +@level3.rem; +@level4.remora : level3; + +//Contrived looking because this is what it took to get it to +// produce an error before. diff --git a/src/tests/errors/tabserrorhandler.ivd b/src/tests/errors/tabserrorhandler.ivd new file mode 100644 index 0000000..b06f846 --- /dev/null +++ b/src/tests/errors/tabserrorhandler.ivd @@ -0,0 +1,8 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + align-x: center; +} diff --git a/src/tests/fixme.ivd b/src/tests/fixme.ivd new file mode 100644 index 0000000..4088bc8 --- /dev/null +++ b/src/tests/fixme.ivd @@ -0,0 +1,66 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window -> trigger-state-model +{ + //First time we're testing models... + position-within: Environment; + layout: vbox; + window-size-strategy: bottom-up; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +.blue-button +{ + padding: 10; + align-a: align-center; + + font-size: 18; + +//state IVD-Item-Hover: //THIS FUCKS UP The anonymous function... + //font-color: #0000ff; + //font: sans-italic; + //color: #808080; +} + +#button -> trigger-state-model : blue-button +{ + position-within: window; + + + text: "Click to use a model trigger to set a model state."; + + +state model.the-model-state: + text: "Toggled on! Click again to toggle off~"; + +state IVD-Item-Hover & ::.IVD-Mouse-Left-Press & !model.the-model-state: + trigger: model.set-the-state; + induce-state: this.onOnce; + +state IVD-Item-Hover & ::.IVD-Mouse-Left-Press & model.the-model-state: + trigger: model.unset-the-state; + induce-state: this.offOnce; + +state onOnce & offOnce: + induce-state: test-done; + trigger: model.passed-hard-test; +} + +# -> trigger-state-model : blue-button +{ + text: "Passed! Click me to quit."; + + +state button.test-done: + position-within: window; + +state IVD-Item-Hover: + color: #ff0000; + +state IVD-Item-Hover & ::.IVD-Mouse-Left-Press & button.test-done: + trigger: IVD-Core-Quit; +} diff --git a/src/tests/fixme1.txt b/src/tests/fixme1.txt new file mode 100644 index 0000000..82b8e92 --- /dev/null +++ b/src/tests/fixme1.txt @@ -0,0 +1 @@ +Need to test what happens with an item references a state on another item that doesn't exist (The item, not the state). Also test this under model context. Currently I'm just getting an exception... diff --git a/src/tests/fixme2.txt b/src/tests/fixme2.txt new file mode 100644 index 0000000..a4ee6fb --- /dev/null +++ b/src/tests/fixme2.txt @@ -0,0 +1 @@ +Apparently IVD-Item-Hover doesn't reset when the mouse leaves the window. THIS IS PRECISELY THE KIND OF THING IVD IS SUPPOSED TO MAKE EASIER diff --git a/src/tests/lightruntime.cpp b/src/tests/lightruntime.cpp new file mode 100644 index 0000000..af4f610 --- /dev/null +++ b/src/tests/lightruntime.cpp @@ -0,0 +1,44 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include + +#include "user_include/cpp/IVD_cpp.h" + +#include "widgets/boxlayout.h" +#include "widgets/stacklayout.h" + +int main(int argc, char** argv) +{ + + if(argc == 1) + { + std::cout << "Usage: lightruntime sourcefile.ivd" << std::endl; + return 0; + } + else if(argc != 2) + { + std::cout << "Too many arguements" << std::endl; + return 0; + } + + const std::string path = argv[1]; + + IVD::bindings::Environment rt; + + rt.register_layout("hbox"); + rt.register_layout("vbox"); + rt.register_layout("stack"); + + const int stat = rt.load_IVD_from_file(path.c_str()); + + if(stat == IVD_STATUS_SUCCESS) + rt.run(); + else if(stat == IVD_STATUS_FILE_NOT_FOUND) + std::cout << "File note found: " << path << std::endl; + else if(stat == IVD_STATUS_COMPILE_ERROR) + std::cout << "Erorrs in code: " << std::endl << rt.get_compiler_errors() << std::endl; + else + std::cout << "An unknown error occoured" << std::endl; + + return 0; +} diff --git a/src/tests/model/MEEEFIXMEEE.ivd b/src/tests/model/MEEEFIXMEEE.ivd new file mode 100644 index 0000000..6b2225e --- /dev/null +++ b/src/tests/model/MEEEFIXMEEE.ivd @@ -0,0 +1,44 @@ +spec bleeding; + +//This is a regression test for a bug where if a sibling object was positioned within +// a layout bound to a model with children. The sibling is then not considered. +//This test shows this by requiring an offset which never gets calculated. +//The geometry itself gets calculated, but that is because the geo-solver is unordered. + +//This test requires a model with children (Not necessarily ones bound to an element) + +#window +{ + position-within: Environment; + width: 640; + height: 400; + window-size-strategy: bottom-up; + title-text: "Etch Kinetic Prototype"; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +//This creates an offset for the items below, enforcing the need for proper model offsetting. +//(Bound to the model because otherwise it's computed *after*, at the time of writing) +# -> timelines +{ + position-within: window.viewport; +} + +#sibling-container -> timelines +{ + position-within: window; + layout: hbox; + color: green; +} + +#playhead-title -> timelines +{ + position-within: sibling-container; + width: 100; + height: 10; + color: blue; +} diff --git a/src/tests/model/bindnum.ivd b/src/tests/model/bindnum.ivd new file mode 100644 index 0000000..b18b309 --- /dev/null +++ b/src/tests/model/bindnum.ivd @@ -0,0 +1,25 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> test-model +{ + position-within: Environment; + + width: model.the-width; + height: model.the-height; + + title-text: "Hover over the window to trigger resize from model."; + + window-size-strategy: bottom-up; + +state IVD-Item-Hover: + trigger: model.update-size; + induce-state: next; + +state next: + title-text: "Huzzah! If the window changed size then we are good to go! Close with X"; + +state next & (IVD-Window-Close | ::.IVD-Scan-X-Press): + trigger: IVD-Core-Quit, model.accept; +} diff --git a/src/tests/model/commonparentdeduction.ivd b/src/tests/model/commonparentdeduction.ivd new file mode 100644 index 0000000..e01cc72 --- /dev/null +++ b/src/tests/model/commonparentdeduction.ivd @@ -0,0 +1,23 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#parent -> test-model +{ + position-within: Environment; + layout: vbox; + +state IVD-Window-Initialized: + trigger: model.set-the-state; + +state oops: + trigger: IVD-Core-Quit, model.accept; +} + +# -> test-model +{ + position-within: parent; + +state model.the-model-state: + induce-state: parent.oops; +} diff --git a/src/tests/model/commonsiblingdeduction.ivd b/src/tests/model/commonsiblingdeduction.ivd new file mode 100644 index 0000000..2fd2f2f --- /dev/null +++ b/src/tests/model/commonsiblingdeduction.ivd @@ -0,0 +1,21 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#main -> test-model +{ + position-within: Environment; + +state IVD-Window-Initialized & model.first-grab: + trigger: model.set-the-state, model.once-only; + +state kill-me-brother & model.first-grab: + trigger: IVD-Core-Quit, model.accept; +} + +# -> test-model +{ +state model.the-model-state & model.first-grab: + induce-state: main.kill-me-brother; +} + diff --git a/src/tests/model/compoundinvalidate.ivd b/src/tests/model/compoundinvalidate.ivd new file mode 100644 index 0000000..3cd89f5 --- /dev/null +++ b/src/tests/model/compoundinvalidate.ivd @@ -0,0 +1,23 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + position-within: Environment; + + text: "Bad touchie"; + +state IVD-Window-Initialized: + induce-state: comp; + +state comp: + unset-state: comp; + +state comp & !blep: + text: "Good touch"; + + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} diff --git a/src/tests/model/deeprootnested.ivd b/src/tests/model/deeprootnested.ivd new file mode 100644 index 0000000..5f38e4a --- /dev/null +++ b/src/tests/model/deeprootnested.ivd @@ -0,0 +1,30 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window -> windows +{ + position-within: Environment; + window-size-strategy: bottom-up; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#child -> windows::child +{ + padding: 10; + +state model.last: + text: clear; + layout: vbox; + +} + +# -> windows::child::deep-kid +{ + position-within: window; + text: model.thine-text; +} diff --git a/src/tests/model/instdestruct.ivd b/src/tests/model/instdestruct.ivd new file mode 100644 index 0000000..5371ba1 --- /dev/null +++ b/src/tests/model/instdestruct.ivd @@ -0,0 +1,35 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +# -> test-model +{ + position-within: window; + + padding: 5; + + text: "Just a model entry"; + +state model.first-grab: + text: "Press N to insert, X to erase, and D to accept"; + +state model.first-grab & ::.IVD-Scan-N-Press: + trigger: model.insert-one; + +state model.first-grab & ::.IVD-Scan-X-Press: + trigger: model.erase-one; + +state model.first-grab & ::.IVD-Scan-D-Press: + trigger: IVD-Core-Quit; +} diff --git a/src/tests/model/modelinstancing.ivd b/src/tests/model/modelinstancing.ivd new file mode 100644 index 0000000..ffb57d9 --- /dev/null +++ b/src/tests/model/modelinstancing.ivd @@ -0,0 +1,17 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#main -> test-model +{ + position-within: Environment; + +state IVD-Window-Initialized: + trigger: model.set-the-state; +} + +# -> test-model +{ +state model.the-model-state: + trigger: IVD-Core-Quit, model.accept; +} diff --git a/src/tests/model/nested.ivd b/src/tests/model/nested.ivd new file mode 100644 index 0000000..79d7133 --- /dev/null +++ b/src/tests/model/nested.ivd @@ -0,0 +1,33 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window -> windows +{ + position-within: Environment; + window-size-strategy: bottom-up; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#child -> windows::child +{ + position-within: window; + text: model.the-text; + + padding: 10; + +state model.last: + text: clear; + layout: vbox; + +} + +# -> windows::child::deep-kid +{ + position-within: child; + text: model.thine-text; +} diff --git a/src/tests/model/notstate.ivd b/src/tests/model/notstate.ivd new file mode 100644 index 0000000..c1f8752 --- /dev/null +++ b/src/tests/model/notstate.ivd @@ -0,0 +1,14 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> test-model +{ + position-within: Environment; + +state !::.IVD-Scan-I-Press: + trigger: IVD-Core-Quit, model.accept; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} diff --git a/src/tests/model/order.ivd b/src/tests/model/order.ivd new file mode 100644 index 0000000..141221b --- /dev/null +++ b/src/tests/model/order.ivd @@ -0,0 +1,46 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +# -> test-model +{ + position-within: window; + align-a: align-center; + + padding: 5; + + text: "BIG BAD ERROR YOU SHOULDN'T SEE THISSSSEEEEE"; + +state model.first-grab: + text: "First item added. Press N to swap."; + +state model.first-grab & model.swapped: + text: "First item, swapped!"; + +state !model.first-grab: + text: "Second item."; + +state !model.first-grab & model.swapped: + text: "YEE hit X to accept the visual result, or manually close the window with the X window button to reject"; + +//These are bound to first-grab because I only want one item +// to execute this. Kinda hacky because this isn't really how they're +// meant to be used. Ideally you'd have a single parent that manages a +// container but this will have to do. +state model.first-grab & ::.IVD-Scan-N-Press: + trigger: model.swap; + +state model.first-grab & ::.IVD-Scan-X-Press & model.swapped: + trigger: model.accept, IVD-Core-Quit; +} diff --git a/src/tests/model/setnum.ivd b/src/tests/model/setnum.ivd new file mode 100644 index 0000000..d5122b7 --- /dev/null +++ b/src/tests/model/setnum.ivd @@ -0,0 +1,15 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> test-model +{ + induce-state: blank; + +state blank: + set: model.fortytwo = 20 * 2 + 2; + induce-state: done; + +state done: + trigger: IVD-Core-Quit, model.accept; +} diff --git a/src/tests/model/singletriggercall.ivd b/src/tests/model/singletriggercall.ivd new file mode 100644 index 0000000..d441ea4 --- /dev/null +++ b/src/tests/model/singletriggercall.ivd @@ -0,0 +1,17 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> test-model +{ +state model.first-grab: + trigger: model.once-only; + unset-state: model.first-grab; + induce-state: done; + +state done: + induce-state: real-done; + +state !model.first-grab & real-done: + trigger: model.accept, IVD-Core-Quit; +} diff --git a/src/tests/model/singletriggercallcompound.ivd b/src/tests/model/singletriggercallcompound.ivd new file mode 100644 index 0000000..438619a --- /dev/null +++ b/src/tests/model/singletriggercallcompound.ivd @@ -0,0 +1,17 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> test-model +{ +state model.first-grab & !done: + trigger: model.once-only; + unset-state: model.first-grab; + induce-state: done; + +state done: + induce-state: real-done; + +state !model.first-grab & real-done: + trigger: model.accept, IVD-Core-Quit; +} diff --git a/src/tests/model/statesuck.ivd b/src/tests/model/statesuck.ivd new file mode 100644 index 0000000..57bd1f0 --- /dev/null +++ b/src/tests/model/statesuck.ivd @@ -0,0 +1,25 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> test-model +{ + position-within: Environment; + window-size-strategy: bottom-up; + + padding: 5; + +state IVD-Window-Initialized: + trigger: model.trig-hook; + +state !model.no-state: + text: "fail"; + +state !model.no-state & model.hook-back: + text: "And now it's changed!"; + trigger: model.accept; + unset-state: model.hook-back; + +state IVD-Window-Close | (model.passed & !model.hook-back): + trigger: IVD-Core-Quit; +} diff --git a/src/tests/model/stringbinding.ivd b/src/tests/model/stringbinding.ivd new file mode 100644 index 0000000..3dd27eb --- /dev/null +++ b/src/tests/model/stringbinding.ivd @@ -0,0 +1,24 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> test-model +{ + position-within: Environment; + window-size-strategy: bottom-up; + + text: model.the-string; + + padding: 10; + +state ::.IVD-Scan-F-Press: + trigger: model.change-text; + induce-state: phase-two; + +state phase-two & ::.IVD-Scan-N-Press: + //This also tests trigger guarantees + trigger: IVD-Core-Quit, model.accept; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} diff --git a/src/tests/model/triggerstate.ivd b/src/tests/model/triggerstate.ivd new file mode 100644 index 0000000..97b5f71 --- /dev/null +++ b/src/tests/model/triggerstate.ivd @@ -0,0 +1,14 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> test-model +{ + position-within: Environment; + +state IVD-Window-Initialized: + trigger: model.set-the-state; + +state model.the-model-state: + trigger: IVD-Core-Quit, model.accept; +} diff --git a/src/tests/recordplayback.cpp b/src/tests/recordplayback.cpp new file mode 100644 index 0000000..f954e02 --- /dev/null +++ b/src/tests/recordplayback.cpp @@ -0,0 +1,58 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "user_include/cpp/IVD_cpp.h" +#include + +#include "widgets/boxlayout.h" +#include "widgets/stacklayout.h" + +int main(int argc, char** argv) +{ + bool status = argc == 4; + + std::string mode; + std::string ivdFile; + std::string path; + + if(status) + { + mode = argv[1]; + ivdFile = argv[2]; + path = argv[3]; + } + + if(mode == "play") + { + reprodyne_play(path.c_str()); + } + else if(mode == "record") + { + reprodyne_record(); + } + else status = false; + + if(!status) + { + std::cerr << "Usage: [play|record] IVD_SOURCE X3TEST_SERIAL" << std::endl; + return -1; + } + + //Run + IVD::bindings::Environment rt; + rt.register_layout("hbox"); + rt.register_layout("vbox"); + rt.register_layout("stack"); + rt.load_IVD_from_file(ivdFile); + rt.run(); + + if(mode == "record") + { + reprodyne_save(path.c_str()); + } + else if(mode == "play") + { + reprodyne_assert_complete_read(); + } + + return 0; +} diff --git a/src/tests/runcompilertests.sh b/src/tests/runcompilertests.sh new file mode 100755 index 0000000..2c16a77 --- /dev/null +++ b/src/tests/runcompilertests.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +#This file is part of the IVD project and is licensed under LGPL-3.0-only + +shopt -s nullglob + +compiler="./ivdserializingcompiler" + +filesTested=0 +filesPassed=0 +filesFailed=0 +filesSkipped=0 + +function printRed() +{ + printf "\e[97m\e[41m$1" +} + +function printGreen() +{ + printf "\e[92m$1" +} + + +function runForDirectory() +{ + echo -e "\e[93mIn \"$1\":" + allIVDfiles=$PWD/$1/*.ivd + + serialPath=$PWD/$1/"ivdserial" + mkdir -p $serialPath + + for f in $allIVDfiles + do + fileName=$(basename $f) + serialFile="$serialPath/$fileName"serial + + if test -f "$serialFile" + then + theDiff=$(diff $serialFile <($compiler $f)) + ((filesTested++)) + + if [ -z "$theDiff" ] + then + ((filesPassed++)) + printGreen "PASS" + else + ((filesFailed++)) + printRed "FAIL" + fi + + echo -e ": $fileName\e[0m" + + else + echo -e "\e[33mSerial file not found for: $fileName" + ((filesSkipped++)) + fi + + + done +} + +runForDirectory "valid" +runForDirectory "errors" + +printf "\e[0m\n" +echo -e "\e[33m$filesSkipped SKIPPED" +echo -e "\e[35m$filesTested TESTED" + + +if [[ $filesPassed -ne 0 ]] +then + printGreen "$(($filesPassed * 100 / $filesTested))%% ($filesPassed) PASSED\e[0m\n" +fi + +if [[ $filesFailed -ne 0 ]] +then + printRed "$(($filesFailed * 100 / $filesTested))%% ($filesFailed) FAILED\e[0m\n" +fi diff --git a/src/tests/runtime/advancedTextLayout.ivd b/src/tests/runtime/advancedTextLayout.ivd new file mode 100644 index 0000000..10bb7d7 --- /dev/null +++ b/src/tests/runtime/advancedTextLayout.ivd @@ -0,0 +1,43 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +#i +{ + position-within: Environment; + layout: inline; + + align-x: align-outer; + justify: align-center; + + +state IVD-Window-Initialized: + window-size-strategy: bottom-up; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#text1 +{ + text: "Henloooo, "; + position-within: i; + + align-y: align-center; + + font: serif; +} + +#text2 +{ + text: "adbanced text formatting!"; + position-within: i; + font-size: 30u; + +state IVD-Item-Hover: + font: serif-bold-italic; + + color: #e2adff; + font-color: #ffffff; +} diff --git a/src/tests/runtime/basicwindow.ivd b/src/tests/runtime/basicwindow.ivd new file mode 100644 index 0000000..463d8b6 --- /dev/null +++ b/src/tests/runtime/basicwindow.ivd @@ -0,0 +1,20 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#item +{ + position-within: Environment; + + width: 300; + height: 100; + + align-x: align-center; + align-y: align-center; + +state IVD-Window-Initialized: + window-size-strategy: bottom-up; + +state IVD-Window-Close: + trigger: ::.IVD-Core-Quit; +} diff --git a/src/tests/runtime/button-array.ivd b/src/tests/runtime/button-array.ivd new file mode 100644 index 0000000..30873cf --- /dev/null +++ b/src/tests/runtime/button-array.ivd @@ -0,0 +1,62 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +//This file is meant to be used as a template for writing a certain +// kind of test. + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +.square +{ + width: 40; + height: 40; + margin: 5; + color: blue; + +state this.IVD-Item-Hover: + color: green; +} + +#row1 +{ + layout: hbox; + position-within: window; +} + +# : square +{ position-within: row1; } +# : square +{ position-within: row1; } +# : square +{ position-within: row1; } +# : square +{ position-within: row1; } +# : square +{ position-within: row1; } + +#row2 +{ + layout: hbox; + position-within: window; +} + +# : square +{ position-within: row2; } +# : square +{ position-within: row2; } +# : square +{ position-within: row2; } +# : square +{ position-within: row2; } +# : square +{ position-within: row2; } diff --git a/src/tests/runtime/compoundStates.ivd b/src/tests/runtime/compoundStates.ivd new file mode 100644 index 0000000..c75b7c7 --- /dev/null +++ b/src/tests/runtime/compoundStates.ivd @@ -0,0 +1,21 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + position-within: Environment; + title-text: "The Demo"; + + window-size-strategy: bottom-up; + + text: "Press Q, U and not E, or just E to change this text~"; + +state +((!::.IVD-Scan-Q-Active & !::.IVD-Scan-U-Active) & ::.IVD-Scan-E-Active) | +((::.IVD-Scan-Q-Active & ::.IVD-Scan-U-Active) & !::.IVD-Scan-E-Active): + text: "Huzzah! Compound states are a thing!"; + +state IVD-Window-Close: + trigger: ::.IVD-Core-Quit; +} diff --git a/src/tests/runtime/compoundinvalidatestatic.ivd b/src/tests/runtime/compoundinvalidatestatic.ivd new file mode 100644 index 0000000..ebc5846 --- /dev/null +++ b/src/tests/runtime/compoundinvalidatestatic.ivd @@ -0,0 +1,25 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + position-within: Environment; + + window-size-strategy: bottom-up; + + +state IVD-Window-Initialized: + text: "Bad touchie"; + induce-state: comp; + +state comp: + unset-state: comp; + +state !comp & !blep: + text: "Good touch"; + + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} diff --git a/src/tests/runtime/concurrenttest.py b/src/tests/runtime/concurrenttest.py new file mode 100644 index 0000000..8c8d1e0 --- /dev/null +++ b/src/tests/runtime/concurrenttest.py @@ -0,0 +1,44 @@ +#This file is part of the IVD project and is licensed under LGPL-3.0-only + +import concurrent.futures +import subprocess +import os +import sys + +nope, cutable = sys.argv + +def do_all(IMES): + def exec(arglist): + try: + subprocess.run(arglist, check=True) + except: + print("Failed for: %s" % (arglist[2])) + print("\n") + + + with concurrent.futures.ThreadPoolExecutor() as executor: + futures = [] + for i in IMES: + futures.append(executor.submit(exec, i)) + + for f in concurrent.futures.as_completed(futures): + pass + +IME = [] + +for fileName in os.listdir(): + base, ext = os.path.splitext(fileName) + if ext == '.ivd': + x3thpath = os.path.join("reprodyne", base + ".x3th") + + #is it even existant + if not os.path.isfile(x3thpath): + print("Could not find x3th for: %s, skipping..." % (fileName)) + continue + + IME.append([cutable, 'play', fileName, x3thpath]) + + + +do_all(IME) + diff --git a/src/tests/runtime/cornertest.ivd b/src/tests/runtime/cornertest.ivd new file mode 100644 index 0000000..ab8508f --- /dev/null +++ b/src/tests/runtime/cornertest.ivd @@ -0,0 +1,32 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#i +{ + position-within: Environment; + layout: vbox; + +state this.IVD-Window-Initialized: + width: 300u; + height: 300u; + window-size-strategy: bottom-up; + +state this.IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#bloop +{ + width: 200u; + height: 100u; + + color: #000000; + + position-within: i; + align-x: align-outer; + align-y: align-outer; + +state this.IVD-Item-Hover: + color: #4286f4; +} diff --git a/src/tests/runtime/defaultTextSize.ivd b/src/tests/runtime/defaultTextSize.ivd new file mode 100644 index 0000000..b8b1b1b --- /dev/null +++ b/src/tests/runtime/defaultTextSize.ivd @@ -0,0 +1,30 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +#i +{ + position-within: Environment; + layout: inline; + + align-x: align-outer; + justify: align-center; + + +state IVD-Window-Initialized: + window-size-strategy: bottom-up; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#text1 +{ + text: "Henloooo, "; + position-within: i; + + align-y: align-center; + + font: serif; +} diff --git a/src/tests/runtime/emptyitemsvboxtest.ivd b/src/tests/runtime/emptyitemsvboxtest.ivd new file mode 100644 index 0000000..b0dbb39 --- /dev/null +++ b/src/tests/runtime/emptyitemsvboxtest.ivd @@ -0,0 +1,30 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + width: 400; + height: 400; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#empty +{ + position-within: window; + color: blue; +} + +#empty2 +{ + position-within: window; + color: green; +} diff --git a/src/tests/runtime/firstivdsdltest.ivd b/src/tests/runtime/firstivdsdltest.ivd new file mode 100644 index 0000000..6fde259 --- /dev/null +++ b/src/tests/runtime/firstivdsdltest.ivd @@ -0,0 +1,76 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + + title-text: "First IVD/SDL test!"; + width: 640u; + height: 400u; + + layout: vbox; + + radio-state: titleCleared, titleColored, titleCentered; + +state titleColored: + title-text: "This window has been colored by F2!"; + +state titleCleared: + title-text: "This window has been cleared by F3!"; + +state titleCentered: + title-text: "This window has been centered by F4!"; + + +state colored: + color: #571eb2; + +state ::.IVD-Key-F1-Active: + title-text: "Good ol' yeller!"; + color: #e8bb09; + +state ::.IVD-Key-F2-Press: + induce-state: colored, titleColored; + +state ::.IVD-Key-F3-Press: + unset-state: colored; + induce-state: titleCleared; + +state ::.IVD-Key-F4-Press: + induce-state: titleCentered; + trigger-state: align-centerNow; + + +state IVD-Window-Initialized: + window-size-strategy: bottom-up; + trigger-state: align-centerNow; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; + +//Trigger states +state align-centerNow: + align-y: align-center; + align-x: align-center; +} + +#abox +{ + color: #251eb2; + width: 100u; + height: 30u; + + position-within: window; + + align-x: align-center; + align-y: align-center; + +state this.IVD-Item-Hover: + color: #C8A2C8; + width: 200, ease-in(100ms, graph(linear, 0 @ 0, 1 @ 1)); + +state ::.IVD-Mouse-Left-Active: + color: #25f221; +} diff --git a/src/tests/runtime/firsttexttest.ivd b/src/tests/runtime/firsttexttest.ivd new file mode 100644 index 0000000..a8bfd93 --- /dev/null +++ b/src/tests/runtime/firsttexttest.ivd @@ -0,0 +1,30 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#i +{ + position-within: Environment; + + //width: 30u; + justify: align-center; + + title-text: "First IVD Text Demo"; + text: "Henlo, IVD!"; + + font: sans; + + color: #e5b8ff; + window-size-strategy: bottom-up; + +state IVD-Item-Hover: + font: mono-bold; + + +state IVD-Window-Initialized: + window-size-strategy: bottom-up; + + +state IVD-Window-Close: + trigger: ::.IVD-Core-Quit; +} diff --git a/src/tests/runtime/freelayout.ivd b/src/tests/runtime/freelayout.ivd new file mode 100644 index 0000000..d5d40c9 --- /dev/null +++ b/src/tests/runtime/freelayout.ivd @@ -0,0 +1,37 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + layout: free-layout; + + width: 300; + height: 300; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +# +{ + position-within: window; + + width: 10; + height: 10; + + color: green; + +state ::.IVD-Scan-I-Active: + color: red; + + trans-x: 100; + trans-y: 100; + +state IVD-Item-Hover: + color: blue; +} diff --git a/src/tests/runtime/freelayout2.ivd b/src/tests/runtime/freelayout2.ivd new file mode 100644 index 0000000..cd836ad --- /dev/null +++ b/src/tests/runtime/freelayout2.ivd @@ -0,0 +1,38 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + layout: free-layout; + + width: 300; + height: 300; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +# +{ + position-within: window; + + width: 100; + height: 200; + + color: green; + + trans-x: 0; + + +state ::.IVD-Scan-I-Active: + color: red; + + trans-x: 50; + +state IVD-Item-Hover: + color: blue; +} diff --git a/src/tests/runtime/fullhover.ivd b/src/tests/runtime/fullhover.ivd new file mode 100644 index 0000000..c353531 --- /dev/null +++ b/src/tests/runtime/fullhover.ivd @@ -0,0 +1,23 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#item +{ + position-within: Environment; + + width: 300; + height: 100; + + align-x: align-center; + align-y: align-center; + +state IVD-Window-Initialized: + window-size-strategy: bottom-up; + +state IVD-Item-Hover: + color: #4286f4; + +state IVD-Window-Close: + trigger: ::.IVD-Core-Quit; +} diff --git a/src/tests/runtime/greedyPrecedence.ivd b/src/tests/runtime/greedyPrecedence.ivd new file mode 100644 index 0000000..d946092 --- /dev/null +++ b/src/tests/runtime/greedyPrecedence.ivd @@ -0,0 +1,33 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + width: 640; + height: 400; + window-size-strategy: bottom-up; + title-text: "WRONG WRONG WRONG"; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + + +#left-most +{ + position-within: window; + color: yellow; + height: 100; +} + +#affff +{ + position-within: window; + height: 100; + fill-precedence-y: greedy; + color: blue; +} diff --git a/src/tests/runtime/greedyPrecedence_var1.ivd b/src/tests/runtime/greedyPrecedence_var1.ivd new file mode 100644 index 0000000..6897ffc --- /dev/null +++ b/src/tests/runtime/greedyPrecedence_var1.ivd @@ -0,0 +1,33 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + width: 640; + height: 400; + window-size-strategy: bottom-up; + title-text: "WRONG WRONG WRONG"; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + + +#left-most +{ + position-within: window; + color: yellow; + fill-precedence-y: greedy; + height: 100; +} + +#affff +{ + position-within: window; + height: 100; + color: blue; +} diff --git a/src/tests/runtime/greedyPrecedence_var2.ivd b/src/tests/runtime/greedyPrecedence_var2.ivd new file mode 100644 index 0000000..8ff1b2f --- /dev/null +++ b/src/tests/runtime/greedyPrecedence_var2.ivd @@ -0,0 +1,34 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + width: 640; + height: 400; + window-size-strategy: bottom-up; + title-text: "WRONG WRONG WRONG"; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + + +#left-most +{ + position-within: window; + color: yellow; + height: 100; +} + +#affff +{ + position-within: window; + height: 100; + fill-precedence-y: greedy; + align-y: align-center; + color: blue; +} diff --git a/src/tests/runtime/greedyPrecedence_var3.ivd b/src/tests/runtime/greedyPrecedence_var3.ivd new file mode 100644 index 0000000..8ff1b2f --- /dev/null +++ b/src/tests/runtime/greedyPrecedence_var3.ivd @@ -0,0 +1,34 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + width: 640; + height: 400; + window-size-strategy: bottom-up; + title-text: "WRONG WRONG WRONG"; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + + +#left-most +{ + position-within: window; + color: yellow; + height: 100; +} + +#affff +{ + position-within: window; + height: 100; + fill-precedence-y: greedy; + align-y: align-center; + color: blue; +} diff --git a/src/tests/runtime/hbox.ivd b/src/tests/runtime/hbox.ivd new file mode 100644 index 0000000..cdd022c --- /dev/null +++ b/src/tests/runtime/hbox.ivd @@ -0,0 +1,24 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + layout: hbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +.theClass +{ + position-within: window; + text: "ayeeee."; + padding: 10; +} + +# : theClass; +# : theClass; +# : theClass; diff --git a/src/tests/runtime/image.ivd b/src/tests/runtime/image.ivd new file mode 100644 index 0000000..31e8790 --- /dev/null +++ b/src/tests/runtime/image.ivd @@ -0,0 +1,35 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + layout: vbox; + + color: #000000; + + title-text: "COOL SPACESHIPS"; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +.frame +{ + padding: 30; + position-within: window; +} + +# : frame +{ + image-path: "jpeg_test_article.jpg"; + padding-bottom: clear; + +} + +# : frame +{ + image-path: "png_test_article.png"; +} diff --git a/src/tests/runtime/nestedboxes.ivd b/src/tests/runtime/nestedboxes.ivd new file mode 100644 index 0000000..5dffe60 --- /dev/null +++ b/src/tests/runtime/nestedboxes.ivd @@ -0,0 +1,51 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + layout: stack; + + width: 300; + height: 300; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#root +{ + position-within: window; + layout: hbox; +} + + +#inner1 +{ + position-within: root; + layout: vbox; + color: CornflowerBlue; +} + +.inner1ItemClass +{ + position-within: inner1; + text: "ayeeee"; + color: blue; + +state IVD-Item-Hover: + color: yellow; +} + +# : inner1ItemClass; +# : inner1ItemClass; + +# +{ + position-within: root; + text: "meep"; +state IVD-Item-Hover: + color: red; +} diff --git a/src/tests/runtime/nonscalardelay.ivd b/src/tests/runtime/nonscalardelay.ivd new file mode 100644 index 0000000..b0fec67 --- /dev/null +++ b/src/tests/runtime/nonscalardelay.ivd @@ -0,0 +1,18 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + position-within: Environment; + window-size-strategy: bottom-up; + + text: "ayee"; + + induce-state: dying, delay(2000ms); + +state dying: + text: "Dying in 3..."; + trigger: IVD-Core-Quit, delay(3000ms); + +} diff --git a/src/tests/runtime/notstatestatic.ivd b/src/tests/runtime/notstatestatic.ivd new file mode 100644 index 0000000..b90c67b --- /dev/null +++ b/src/tests/runtime/notstatestatic.ivd @@ -0,0 +1,21 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + position-within: Environment; + + window-size-strategy: bottom-up; + padding: 10; + + title-text: "Fail"; + text: "You should never see this text"; + +state !::.IVD-Scan-I-Press: + title-text: "Happy"; + text: "Pass!"; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} diff --git a/src/tests/runtime/popoutresize.ivd b/src/tests/runtime/popoutresize.ivd new file mode 100644 index 0000000..f6b3129 --- /dev/null +++ b/src/tests/runtime/popoutresize.ivd @@ -0,0 +1,58 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#main +{ + position-within: Environment; + title-text: "Press Q to pop the box out"; + size-x: 300; + size-y: 300; + + visibility: disable; + + layout: vbox; + + +state doneInit: + visibility: enable; + +state IVD-Window-Initialized: + align-y: align-center; + align-x: align-center; + window-size-strategy: bottom-up; + induce-state: doneInit; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#widget +{ + size-x: 100; + size-y: 100; + + color: #4286f4; + + position-within: main; + + align-x: align-center; + align-y: align-center; + +state popped-out: + //color: #0d3c87; + position-within: Environment; + + +state ::.IVD-Scan-Q-Press: + induce-state: popped-out; + +state ::.IVD-Scan-W-Press: + unset-state: popped-out; + +state IVD-Window-Initialized: + window-size-strategy: bottom-up; + +state IVD-Window-Close: + unset-state: popped-out; +} diff --git a/src/tests/runtime/reprodyne/advancedTextLayout.x3th b/src/tests/runtime/reprodyne/advancedTextLayout.x3th new file mode 100644 index 0000000..98c905a Binary files /dev/null and b/src/tests/runtime/reprodyne/advancedTextLayout.x3th differ diff --git a/src/tests/runtime/reprodyne/basicwindow.ivd b/src/tests/runtime/reprodyne/basicwindow.ivd new file mode 100644 index 0000000..c5531e7 Binary files /dev/null and b/src/tests/runtime/reprodyne/basicwindow.ivd differ diff --git a/src/tests/runtime/reprodyne/compoundStates.x3th b/src/tests/runtime/reprodyne/compoundStates.x3th new file mode 100644 index 0000000..b37d868 Binary files /dev/null and b/src/tests/runtime/reprodyne/compoundStates.x3th differ diff --git a/src/tests/runtime/reprodyne/compoundinvalidatestatic.x3th b/src/tests/runtime/reprodyne/compoundinvalidatestatic.x3th new file mode 100644 index 0000000..52211e2 Binary files /dev/null and b/src/tests/runtime/reprodyne/compoundinvalidatestatic.x3th differ diff --git a/src/tests/runtime/reprodyne/cornertest.x3th b/src/tests/runtime/reprodyne/cornertest.x3th new file mode 100644 index 0000000..3ff3f20 Binary files /dev/null and b/src/tests/runtime/reprodyne/cornertest.x3th differ diff --git a/src/tests/runtime/reprodyne/defaultTextSize.x3th b/src/tests/runtime/reprodyne/defaultTextSize.x3th new file mode 100644 index 0000000..1767f01 Binary files /dev/null and b/src/tests/runtime/reprodyne/defaultTextSize.x3th differ diff --git a/src/tests/runtime/reprodyne/emptyitemsvboxtest.x3th b/src/tests/runtime/reprodyne/emptyitemsvboxtest.x3th new file mode 100644 index 0000000..d27433b Binary files /dev/null and b/src/tests/runtime/reprodyne/emptyitemsvboxtest.x3th differ diff --git a/src/tests/runtime/reprodyne/firstivdsdltest.x3th b/src/tests/runtime/reprodyne/firstivdsdltest.x3th new file mode 100644 index 0000000..f2ece4c Binary files /dev/null and b/src/tests/runtime/reprodyne/firstivdsdltest.x3th differ diff --git a/src/tests/runtime/reprodyne/firsttexttest.x3th b/src/tests/runtime/reprodyne/firsttexttest.x3th new file mode 100644 index 0000000..9b5d0b9 Binary files /dev/null and b/src/tests/runtime/reprodyne/firsttexttest.x3th differ diff --git a/src/tests/runtime/reprodyne/freelayout.x3th b/src/tests/runtime/reprodyne/freelayout.x3th new file mode 100644 index 0000000..a0512d7 Binary files /dev/null and b/src/tests/runtime/reprodyne/freelayout.x3th differ diff --git a/src/tests/runtime/reprodyne/freelayout2.x3th b/src/tests/runtime/reprodyne/freelayout2.x3th new file mode 100644 index 0000000..82d8444 Binary files /dev/null and b/src/tests/runtime/reprodyne/freelayout2.x3th differ diff --git a/src/tests/runtime/reprodyne/fullhover.x3th b/src/tests/runtime/reprodyne/fullhover.x3th new file mode 100644 index 0000000..87c2226 Binary files /dev/null and b/src/tests/runtime/reprodyne/fullhover.x3th differ diff --git a/src/tests/runtime/reprodyne/greedyPrecedence.x3th b/src/tests/runtime/reprodyne/greedyPrecedence.x3th new file mode 100644 index 0000000..0859f0c Binary files /dev/null and b/src/tests/runtime/reprodyne/greedyPrecedence.x3th differ diff --git a/src/tests/runtime/reprodyne/greedyPrecedence_var1.x3th b/src/tests/runtime/reprodyne/greedyPrecedence_var1.x3th new file mode 100644 index 0000000..9eced6a Binary files /dev/null and b/src/tests/runtime/reprodyne/greedyPrecedence_var1.x3th differ diff --git a/src/tests/runtime/reprodyne/greedyPrecedence_var2.x3th b/src/tests/runtime/reprodyne/greedyPrecedence_var2.x3th new file mode 100644 index 0000000..9b815ef Binary files /dev/null and b/src/tests/runtime/reprodyne/greedyPrecedence_var2.x3th differ diff --git a/src/tests/runtime/reprodyne/greedyPrecedence_var3.x3th b/src/tests/runtime/reprodyne/greedyPrecedence_var3.x3th new file mode 100644 index 0000000..3a8c26c Binary files /dev/null and b/src/tests/runtime/reprodyne/greedyPrecedence_var3.x3th differ diff --git a/src/tests/runtime/reprodyne/hbox.x3th b/src/tests/runtime/reprodyne/hbox.x3th new file mode 100644 index 0000000..9a8602b Binary files /dev/null and b/src/tests/runtime/reprodyne/hbox.x3th differ diff --git a/src/tests/runtime/reprodyne/image.x3th b/src/tests/runtime/reprodyne/image.x3th new file mode 100644 index 0000000..b2c35ef Binary files /dev/null and b/src/tests/runtime/reprodyne/image.x3th differ diff --git a/src/tests/runtime/reprodyne/nestedboxes.x3th b/src/tests/runtime/reprodyne/nestedboxes.x3th new file mode 100644 index 0000000..b2492df Binary files /dev/null and b/src/tests/runtime/reprodyne/nestedboxes.x3th differ diff --git a/src/tests/runtime/reprodyne/nonscalardelay.x3th b/src/tests/runtime/reprodyne/nonscalardelay.x3th new file mode 100644 index 0000000..c9f36b0 Binary files /dev/null and b/src/tests/runtime/reprodyne/nonscalardelay.x3th differ diff --git a/src/tests/runtime/reprodyne/notstatestatic.x3th b/src/tests/runtime/reprodyne/notstatestatic.x3th new file mode 100644 index 0000000..cdd96c6 Binary files /dev/null and b/src/tests/runtime/reprodyne/notstatestatic.x3th differ diff --git a/src/tests/runtime/reprodyne/popoutresize.x3th b/src/tests/runtime/reprodyne/popoutresize.x3th new file mode 100644 index 0000000..7d1bed3 Binary files /dev/null and b/src/tests/runtime/reprodyne/popoutresize.x3th differ diff --git a/src/tests/runtime/reprodyne/sizeitemvboxtest.x3th b/src/tests/runtime/reprodyne/sizeitemvboxtest.x3th new file mode 100644 index 0000000..afbd136 Binary files /dev/null and b/src/tests/runtime/reprodyne/sizeitemvboxtest.x3th differ diff --git a/src/tests/runtime/reprodyne/sizeitemvboxtest_var1.x3th b/src/tests/runtime/reprodyne/sizeitemvboxtest_var1.x3th new file mode 100644 index 0000000..a116748 Binary files /dev/null and b/src/tests/runtime/reprodyne/sizeitemvboxtest_var1.x3th differ diff --git a/src/tests/runtime/reprodyne/stacklayout.x3th b/src/tests/runtime/reprodyne/stacklayout.x3th new file mode 100644 index 0000000..a9842c3 Binary files /dev/null and b/src/tests/runtime/reprodyne/stacklayout.x3th differ diff --git a/src/tests/runtime/reprodyne/vbox.x3th b/src/tests/runtime/reprodyne/vbox.x3th new file mode 100644 index 0000000..7996410 Binary files /dev/null and b/src/tests/runtime/reprodyne/vbox.x3th differ diff --git a/src/tests/runtime/sizeitemvboxtest.ivd b/src/tests/runtime/sizeitemvboxtest.ivd new file mode 100644 index 0000000..afa95d0 --- /dev/null +++ b/src/tests/runtime/sizeitemvboxtest.ivd @@ -0,0 +1,31 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + width: 400; + height: 400; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#empty +{ + position-within: window; + color: blue; +} + +#empty2 +{ + position-within: window; + color: green; + height: 20; +} diff --git a/src/tests/runtime/sizeitemvboxtest_var1.ivd b/src/tests/runtime/sizeitemvboxtest_var1.ivd new file mode 100644 index 0000000..82de8e4 --- /dev/null +++ b/src/tests/runtime/sizeitemvboxtest_var1.ivd @@ -0,0 +1,31 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + width: 400; + height: 400; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#empty +{ + position-within: window; + color: blue; + height: 20; +} + +#empty2 +{ + position-within: window; + color: green; +} diff --git a/src/tests/runtime/stacklayout.ivd b/src/tests/runtime/stacklayout.ivd new file mode 100644 index 0000000..4304337 --- /dev/null +++ b/src/tests/runtime/stacklayout.ivd @@ -0,0 +1,48 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + + +spec bleeding; + + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + layout: stack; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +//Horizontal bar (Should end up behind vertical) +# +{ + position-within: window; + + width: 100; + height: 10; + + align-y: align-center; + + color: blue; + +state IVD-Item-Hover: + color: red; +} + +//Vertical bar (Should always be on top) +# +{ + position-within: window; + + height: 100; + width: 10; + + align-x: align-center; + + color: green; + +state IVD-Item-Hover: + color: red; +} diff --git a/src/tests/runtime/unvalidated/nestedboxes-variant1.ivd b/src/tests/runtime/unvalidated/nestedboxes-variant1.ivd new file mode 100644 index 0000000..2445fa1 --- /dev/null +++ b/src/tests/runtime/unvalidated/nestedboxes-variant1.ivd @@ -0,0 +1,63 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + layout: stack; + + width: 300; + height: 300; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#root +{ + position-within: window; + layout: hbox; +} + + +#inner1 +{ + position-within: root; + layout: vbox; + + width: 50; + + + color: CornflowerBlue; + +state IVD-Item-Hover: + color: green; +} + +.inner1ItemClass +{ + position-within: inner1; + text: "ayeeee"; + color: blue; + +state IVD-Item-Hover: + color: yellow; +} + +# : inner1ItemClass; +# : inner1ItemClass +{ + align-x: align-right; +} + +# +{ + position-within: root; + text: "meep"; + align-x: align-left; + +state IVD-Item-Hover: + color: red; +} diff --git a/src/tests/runtime/unvalidated/wtf/bugger.ivd b/src/tests/runtime/unvalidated/wtf/bugger.ivd new file mode 100644 index 0000000..d25fdfd --- /dev/null +++ b/src/tests/runtime/unvalidated/wtf/bugger.ivd @@ -0,0 +1,62 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + width: 640; + height: 400; + + layout: stack; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +#main-content-container +{ + position-within: window; + layout: hbox; + +} + +//#welcome; + +#sidebar +{ + position-within: main-content-container; + layout: vbox; +} + +.menu-item +{ + position-within: sidebar; + text: "AYEEEEE"; + + padding: 10; + + trans-x: -width; + + induce-state: animating; + +state animating: + trans-x: 0, ease-in(1000ms, graph(linear, 0 @ 0, 1 @ 1)); + + +state IVD-Item-Hover & ::.IVD-Mouse-Left-Press: + +state IVD-Item-Hover: + color: purple; +} + +# : menu-item; +# : menu-item; + +#content +{ + position-within: main-content-container; + text: "fiiiiick"; +} diff --git a/src/tests/runtime/unvalidated/wtf/constraint.ivd b/src/tests/runtime/unvalidated/wtf/constraint.ivd new file mode 100644 index 0000000..7486ae9 --- /dev/null +++ b/src/tests/runtime/unvalidated/wtf/constraint.ivd @@ -0,0 +1,23 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +//This should be resizable horizontally. +# +{ + declare scalar: custom-width = 0; + + position-within: Environment; + window-size-strategy: top-down; + + width: [custom-width]; + height: width / 2; + +state IVD-Window-Initialized: + set: width = 300; + window-size-strategy: bottom-up; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} diff --git a/src/tests/runtime/unvalidated/wtf/expandtest.ivd b/src/tests/runtime/unvalidated/wtf/expandtest.ivd new file mode 100644 index 0000000..f3fbc3c --- /dev/null +++ b/src/tests/runtime/unvalidated/wtf/expandtest.ivd @@ -0,0 +1,61 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + + height: 400; + width: 400; + + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +.meep +{ + position-within: window; + height: 30; +} + +.cell-greedyer +{ + position-within: window; + + //height: min = 20; + + color: purple; + +state IVD-Item-Hover: + color: orange; + +state !greedy & IVD-Item-Hover & ::.IVD-Mouse-Left-Press: + induce-state: greedy; + +state greedy & IVD-Item-Hover & ::.IVD-Mouse-Right-Press: + unset-state: greedy; + +state greedy: + color: cyan; +} + +# : cell-greedyer; + +# : meep +{ + color: blue; +} + +# : cell-greedyer; + +# : meep +{ + color: green; +} + +# : cell-greedyer; diff --git a/src/tests/runtime/vbox.ivd b/src/tests/runtime/vbox.ivd new file mode 100644 index 0000000..1036b96 --- /dev/null +++ b/src/tests/runtime/vbox.ivd @@ -0,0 +1,24 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + layout: vbox; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} + +.theClass +{ + position-within: window; + text: "ayeeee."; + padding: 10; +} + +# : theClass; +# : theClass; +# : theClass; diff --git a/src/tests/serializingcompiler.cpp b/src/tests/serializingcompiler.cpp new file mode 100644 index 0000000..80d7620 --- /dev/null +++ b/src/tests/serializingcompiler.cpp @@ -0,0 +1,166 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "compiler.h" +#include "statekey.h" //For the << path thing + +#include +#include +#include + +using IVD::operator<<; //Never done that before. + + +void printOutAttributes(IVD::Compiler& comper, IVD::Element elem) +{ + std::cout << "=================================Element========================================" << std::endl; + std::cout << "Path: " << elem.getPath() << std::endl; + + if(elem.getModelPath().size()) + std::cout << "Model " << elem.getModelPath() << std::endl; + + //Now the fun part... + std::cout << "------------------------------------------Body" << std::endl; + + auto printAttrs = [&](const IVD::ReferenceAttributeSet& attrSet, std::ostream& cout) + { + if(attrSet.declareModifiers.size()) + { + for(auto pair : attrSet.declareModifiers) + cout << "--Declared Variable: " << pair.first << std::endl + << pair.second.generatePrintout() << std::endl; + } + + if(attrSet.setModifiers.size()) + { + for(auto pair : attrSet.setModifiers) + cout << "--Set Target: " << pair.first.generatePrintout() << std::endl + << pair.second.generatePrintout() << std::endl; + } + + for(int i = 0; i != IVD::AttributeKey::AttributeCount; ++i) + { + auto attr = attrSet.attr[i]; + if(!attr.active) continue; + + cout << "------------Attr Key: " << comper.getLiteralForSymbol(i) << std::endl; + + if(attr.delay) + cout << "Delay: " << *attr.delay << "ms" << std::endl; + + if(attr.ease) + cout << "Ease-In: " << attr.ease->generatePrintout() << std::endl; + + if(attr.clear) + cout << "Clear Inherit" << std::endl; + + if(attr.property) + cout << "Property: " << comper.getLiteralForSymbol(*attr.property) << std::endl; + + + if(attr.starting) + cout << "Starting Expression" << std::endl + << attr.starting->generatePrintout() << std::endl; + + if(attr.min) + cout << "Min Constraint" << std::endl + << attr.min->generatePrintout() << std::endl; + + if(attr.max) + cout << "Max Constraint" << std::endl + << attr.max->generatePrintout() << std::endl; + + if(attr.expr) + cout << "Main Constraint" << std::endl + << attr.expr->generatePrintout() << std::endl; + + + if(attr.color) + cout << "Color: " << attr.color->generateHexPrint() << " (" + << attr.color->generateDecPrint() << ")" << std::endl; + + + if(attr.literal) + cout << "Literal: \"" << *attr.literal << "\"" << std::endl; + + if(attr.singleKey) + cout << "Single Key: " << attr.singleKey->generatePrintout() << std::endl; + + if(attr.keys.size()) + { + cout << "--Key List" << std::endl; + for(auto key : attr.keys) + cout << key.generatePrintout() << std::endl; + } + + if(attr.literalList.size()) + { + cout << "--Literal List" << std::endl; + for(auto literal : attr.literalList) + cout << literal << std::endl; + } + } + }; + + std::map virtualStates; + + for(auto vskp : elem.getVirtualKeys()) + virtualStates[vskp.proxyStateKeyPrecursor] = vskp; + + std::cout << "-----------------------------State default" << std::endl; + printAttrs(elem.getDefaultAttr(), std::cout); + + std::vector orderedStatePrintouts; + orderedStatePrintouts.insert(orderedStatePrintouts.end(), elem.getKeyedAttributeMap().size(), ""); + + for(auto pair : elem.getKeyedAttributeMap()) + { + auto vskpi = virtualStates.find(pair.first); + const int veryImportantOrdinalPosition = pair.second; + + std::stringstream cout; + + cout << "-----------------------------"; + if(vskpi == virtualStates.end()) + cout << "State Key: " << pair.first.generatePrintout() << std::endl; + else + cout << "State Expression: " << std::endl + << vskpi->second.generatePrintout() << std::endl; // >:3c + + printAttrs(*elem.at(pair.second).attrs, cout); + orderedStatePrintouts.at(veryImportantOrdinalPosition) = cout.str(); + } + + for(const std::string& printout : orderedStatePrintouts) std::cout << printout; +} + +int main(int argc, char** argv) +{ + //Get args + if(argc == 1) + { + std::cout << "Usage: ivdserialcompiler sourcefile.ivd" << std::endl; + return 0; + } + else if(argc != 2) + { + std::cout << "Invalid number of arguments" << std::endl; + return 0; + } + + IVD::Compiler comp; + comp.compileFile(argv[1]); + + if(comp.getErrorMessages().size()) + { + std::cout << "The IVD compiler has encountered the following " + << (comp.getErrorMessages().size() == 1 ? "error:" : "errors") + << std::endl; + for(auto msg : comp.getErrorMessages()) std::cout << msg << std::endl; + + std::cout << "---------------------------------------------------" << std::endl; + } + + for(IVD::Element elem : comp.getElements()) printOutAttributes(comp, elem); + + return 0; +} diff --git a/src/tests/test-scaffold.ivd b/src/tests/test-scaffold.ivd new file mode 100644 index 0000000..d522910 --- /dev/null +++ b/src/tests/test-scaffold.ivd @@ -0,0 +1,15 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +//This file is meant to be used as a template for writing a certain +// kind of test. + +#window +{ + position-within: Environment; + window-size-strategy: bottom-up; + +state IVD-Window-Close: + trigger: IVD-Core-Quit; +} diff --git a/src/tests/thisshouldnotcompile.ivd b/src/tests/thisshouldnotcompile.ivd new file mode 100644 index 0000000..633a0d0 --- /dev/null +++ b/src/tests/thisshouldnotcompile.ivd @@ -0,0 +1,148 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +#window + +{ + + position-within: Environment; + + + + title-text: "First IVD/SDL test!"; + + width: 640u; + + height: 400u; + + + + layout: vbox; + + + + radio-state: titleCleared, titleColored, titleCentered; + + + +state titleColored: + + title-text: "This window has been colored by F2!"; + + + +state titleCleared: + + title-text: "This window has been cleared by F3!"; + + + +state titleCentered: + + title-text: "This window has been centered by F4!"; + + + + + +state colored: + + color: #571eb2; + + + +state ::.IVD-Key-F1-Active: + + title-text: "Good ol' yeller!"; + + color: #e8bb09; + + + +state ::.IVD-Key-F2-Press: + + induce-state: colored, titleColored; + + + +state ::.IVD-Key-F3-Press: + + unset-state: colored; + + induce-state: titleCleared; + + + +state ::.IVD-Key-F4-Press: + + induce-state: titleCentered; + + trigger-state: align-centerNow; + + + + + +state IVD-Window-Initialized: + + window-size-strategy: bottom-up; + + trigger-state: align-centerNow; + + + +state IVD-Window-Close: + + trigger: IVD-Core-Quit; + + + +//Trigger states + +state align-centerNow: + + align-y: align-center; + + align-x: align-center; + +} + + + +#abox + +{ + + color: #251eb2; + + width: 100u; + + height: 30u; + + + + position-within: window; + + + + align-x: align-center; + + align-y: align-center; + + + +state this.IVD-Item-Hover: + + color: #C8A2C8; + + width: 200, ease-in(linear, graph(0 @ 0, 1 @ 1)); + + + +state IVD-Mouse-Left-Active: + + color: #25f221; + +} diff --git a/src/tests/valid/animationgraph.ivd b/src/tests/valid/animationgraph.ivd new file mode 100644 index 0000000..d8b1fc1 --- /dev/null +++ b/src/tests/valid/animationgraph.ivd @@ -0,0 +1,12 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + declare graph: testGraph = graph(linear, 0.1 @ 0.4, + 0.4 @ 0.6, + 1 @ 0.7); + + width: 20u, ease-in(500ms, testGraph); +} diff --git a/src/tests/valid/articleValidVariant0.ivd b/src/tests/valid/articleValidVariant0.ivd new file mode 100755 index 0000000..18a2250 --- /dev/null +++ b/src/tests/valid/articleValidVariant0.ivd @@ -0,0 +1,64 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +//This is the first IVD file to be tested. Ever. (It has been revised tho) + +.okayClass +{ + cell-names: blue, red, green, yellow; + +state this.hovered: + height: 20; +} + +.youDidntTryatAllClass; + + +#anElement-> theModel : okayClass, youDidntTryatAllClass //This is comment +{ //Another comment + + declare expression: welp = 3 * 10; + declare expression: help = 10; + + layout: sierraGallery; + position-within: ::header.cell; + + margin: 3; + margin-right: 10; + margin-left: 23; + + padding: clear; + + width: model::mylay.mywidth; + width: min = 45; + height: 50 + 2 * 3; + width: (40); + trans-x: (3) * 4; + trans-y: (7 + 3) * 3; + + orientation: adjacent-is-vertical; + +state this.hovered: + borderless: disable; +} //Comment comment comment + + +//Adding because at some point you couldn't have single character +// element names, boo! +#i : okayClass +{ + margin: pass; + padding: 3 + 300; +state hovered: + height: clear; +} + +#n +{ +} + +#x; + +# : okayClass; +# : okayClass; diff --git a/src/tests/valid/attrInExpression.ivd b/src/tests/valid/attrInExpression.ivd new file mode 100755 index 0000000..3c2ea22 --- /dev/null +++ b/src/tests/valid/attrInExpression.ivd @@ -0,0 +1,11 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + width: this.height * 4; + height: rel-x * 4; + trans-x: other.height; + trans-y: other.eleven; +} diff --git a/src/tests/valid/basic.ivd b/src/tests/valid/basic.ivd new file mode 100755 index 0000000..34a9b0a --- /dev/null +++ b/src/tests/valid/basic.ivd @@ -0,0 +1,8 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + +} diff --git a/src/tests/valid/booleaninheritance.ivd b/src/tests/valid/booleaninheritance.ivd new file mode 100755 index 0000000..5b36217 --- /dev/null +++ b/src/tests/valid/booleaninheritance.ivd @@ -0,0 +1,16 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.gree +{ + borderless: enable; +} + +.gred +{ + borderless: disable; +} + +# : gree; +# : gred; diff --git a/src/tests/valid/cellalignmnet.ivd b/src/tests/valid/cellalignmnet.ivd new file mode 100755 index 0000000..79af699 --- /dev/null +++ b/src/tests/valid/cellalignmnet.ivd @@ -0,0 +1,33 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#aCenter +{ + align-y: align-center; + align-x: align-center; +} + +#left +{ + align-y: align-inner; + align-x: align-inner; +} + +#right +{ + align-y: align-outer; + align-x: align-outer; +} + +.inhert +{ + align-y: align-center; +} + +.breakit +{ + align-x: align-center; +} + +# : inhert, breakit; diff --git a/src/tests/valid/cellnames.ivd b/src/tests/valid/cellnames.ivd new file mode 100755 index 0000000..9e293ba --- /dev/null +++ b/src/tests/valid/cellnames.ivd @@ -0,0 +1,16 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.cellnameroot +{ + cell-names:fuck ,me, in; +} + +# : cellnameroot; + +# +{ + cell-names: oh,baby, its, + triple; +} diff --git a/src/tests/valid/classheaders.ivd b/src/tests/valid/classheaders.ivd new file mode 100755 index 0000000..ba7e1d4 --- /dev/null +++ b/src/tests/valid/classheaders.ivd @@ -0,0 +1,13 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.aclass; +.bclass; + +# : aclass; +# : aclass, bclass; + +//Not really sure what else to test... Maybe throw in a model? + +# -> fictionalModel : aclass, bclass; diff --git a/src/tests/valid/classprecedence.ivd b/src/tests/valid/classprecedence.ivd new file mode 100644 index 0000000..c8d3323 --- /dev/null +++ b/src/tests/valid/classprecedence.ivd @@ -0,0 +1,16 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.class1 +{ + text: "This is class 1"; +} + +.class2 +{ + text: "This is class 2"; +} + +#onetwo : class1, class2; +#twoone : class2, class1; diff --git a/src/tests/valid/classusingmodelsforinstance.ivd b/src/tests/valid/classusingmodelsforinstance.ivd new file mode 100755 index 0000000..961bddc --- /dev/null +++ b/src/tests/valid/classusingmodelsforinstance.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.a1class1 +{ + width: model.widthat; //shit shit shit models can have this names fuck fuck +} + +# -> theModel : a1class1; diff --git a/src/tests/valid/clearinherit.ivd b/src/tests/valid/clearinherit.ivd new file mode 100644 index 0000000..8f75d67 --- /dev/null +++ b/src/tests/valid/clearinherit.ivd @@ -0,0 +1,15 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.gotClass +{ + padding-a-in: 10; +} + +# : gotClass +{ + padding-a-in: clear; + margin-a-in: clear; + margin-a-out: clear, 1; //Kind of redundant... +} diff --git a/src/tests/valid/colorattr.ivd b/src/tests/valid/colorattr.ivd new file mode 100644 index 0000000..526c7bc --- /dev/null +++ b/src/tests/valid/colorattr.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + color: #7f0fff; + border-color: #0fdd43; + font-color: #f26546; +} diff --git a/src/tests/valid/colorliterals.ivd b/src/tests/valid/colorliterals.ivd new file mode 100644 index 0000000..3b910d0 --- /dev/null +++ b/src/tests/valid/colorliterals.ivd @@ -0,0 +1,9 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# { color: blue; } +# { color: cornflower blue; } +# { color: gainsboro; } +# { color: green; } +# { color: purple; } diff --git a/src/tests/valid/compoundstatekeys.ivd b/src/tests/valid/compoundstatekeys.ivd new file mode 100755 index 0000000..eb88860 --- /dev/null +++ b/src/tests/valid/compoundstatekeys.ivd @@ -0,0 +1,9 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ +state (effme | duckme) & helpme: + height: 42; +} diff --git a/src/tests/valid/constraintcase1.ivd b/src/tests/valid/constraintcase1.ivd new file mode 100644 index 0000000..6b1a594 --- /dev/null +++ b/src/tests/valid/constraintcase1.ivd @@ -0,0 +1,49 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.scrollbar +{ + declare expression: scrollRatio = @::barArea.size-a / [@::bar.size-a]; + declare expression: offsetFactor = [@::bar.trans-a] / (@::barArea.size-a - @::bar.size-a); + + layout: vbox; + cell-names: upperButter, middle, lowerButton; +} + +@scrollbar.barArea +{ + position-within: @.middle; + layout: free-layout; + size-a: 100%; +} + +@scrollbar.bar +{ + position-within: @.barArea; + + size-a: start = 100%, min = 4%, max = 100%; + trans-a: start = 0, min = 0, max = @::barArea.size-a - this.size-a; +} + +#myBar : scrollbar; + +#myView +{ + trans-a: this.size-a * [myBar.offsetFactor]; + +state this.geometry-updated: + set: myBar.scrollRatio = myPort.size-a / this.size-a; + +state key.specialDownButtonThing: + set: this.trans-a = this.trans-a + 20; + +state key.special-up-button-thing: + set: this.trans-a = this.trans-a - 20; +} + +#mySecondDependency +{ + trans-o: this.size-o * myBar.offsetFactor; + width: 10u / 2 + 3%; +} diff --git a/src/tests/valid/declare.ivd b/src/tests/valid/declare.ivd new file mode 100644 index 0000000..63c07e7 --- /dev/null +++ b/src/tests/valid/declare.ivd @@ -0,0 +1,9 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#elem -> aModel +{ + declare expression: bloopy = 7 * [model::zooper.doop]; + declare expression: zoopy = 8; +} diff --git a/src/tests/valid/delay.ivd b/src/tests/valid/delay.ivd new file mode 100644 index 0000000..ba7f22a --- /dev/null +++ b/src/tests/valid/delay.ivd @@ -0,0 +1,8 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + height: delay(10ms); +} diff --git a/src/tests/valid/dimensions.ivd b/src/tests/valid/dimensions.ivd new file mode 100755 index 0000000..7dc867f --- /dev/null +++ b/src/tests/valid/dimensions.ivd @@ -0,0 +1,17 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.awidth +{ + width: 10; +} + +.aheight +{ + height: 10; +} + +# : awidth; +# : aheight; +# : awidth, aheight; diff --git a/src/tests/valid/flatexpression.ivd b/src/tests/valid/flatexpression.ivd new file mode 100644 index 0000000..d0eef36 --- /dev/null +++ b/src/tests/valid/flatexpression.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + padding-o-in: 4 + 9 * 7; + padding-o-out: 4 + 9 * 7 + 9 / 2; + padding-o-out: 4 - 9 * 7 - 9 / 2; +} diff --git a/src/tests/valid/inheritStateOverrideOrder.ivd b/src/tests/valid/inheritStateOverrideOrder.ivd new file mode 100644 index 0000000..912388c --- /dev/null +++ b/src/tests/valid/inheritStateOverrideOrder.ivd @@ -0,0 +1,27 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +.test +{ + text: "This should never come through."; + +state hep: + color: green; + +state blep: + title-text: "This either"; + +} + +# : test +{ + text: "Correct."; + +state blep: + title-text: "Corrrrrect."; + +state hep: + color: blue; +} diff --git a/src/tests/valid/inheritanceOverriding.ivd b/src/tests/valid/inheritanceOverriding.ivd new file mode 100644 index 0000000..41d89ec --- /dev/null +++ b/src/tests/valid/inheritanceOverriding.ivd @@ -0,0 +1,13 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.test +{ + text: "This should never come through."; +} + +# : test +{ + text: "Correct."; +} diff --git a/src/tests/valid/inheritedvirtualstates.ivd b/src/tests/valid/inheritedvirtualstates.ivd new file mode 100644 index 0000000..221b656 --- /dev/null +++ b/src/tests/valid/inheritedvirtualstates.ivd @@ -0,0 +1,19 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + + +.parent +{ +state x & y: + color: blue; + +state !a: + text: "eyeyeyeyee"; +} + +# : parent +{ +state x & y: + color: green; +} diff --git a/src/tests/valid/ivdserial/animationgraph.ivdserial b/src/tests/valid/ivdserial/animationgraph.ivdserial new file mode 100644 index 0000000..b41e171 --- /dev/null +++ b/src/tests/valid/ivdserial/animationgraph.ivdserial @@ -0,0 +1,10 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: size-a +Ease-In: Time: 500ms. Mode: Linear, samples: 0.4 @ 0.1, 0.6 @ 0.4, 0.7 @ 1. +Main Constraint + | +20.000000u + diff --git a/src/tests/valid/ivdserial/articleValidVariant0.ivdserial b/src/tests/valid/ivdserial/articleValidVariant0.ivdserial new file mode 100644 index 0000000..08c026a --- /dev/null +++ b/src/tests/valid/ivdserial/articleValidVariant0.ivdserial @@ -0,0 +1,178 @@ +=================================Element======================================== +Path: anElement +Model theModel +------------------------------------------Body +-----------------------------State default +--Declared Variable: help + | +10.000000i + +--Declared Variable: welp + | + .....(*)...... + | | +3.000000i 10.000000i + +------------Attr Key: position-within +Single Key: global::header.cell +------------Attr Key: trans-o +Main Constraint + | + ........(*)........ + | | + .....(+)..... 3.000000i + | | +7.000000i 3.000000i + +------------Attr Key: trans-a +Main Constraint + | + .....(*)..... + | | +3.000000i 4.000000i + +------------Attr Key: margin-o-out +Main Constraint + | +3.000000i + +------------Attr Key: margin-o-in +Main Constraint + | +3.000000i + +------------Attr Key: margin-a-in +Main Constraint + | +23.000000i + +------------Attr Key: margin-a-out +Main Constraint + | +10.000000i + +------------Attr Key: padding-o-out +Clear Inherit +------------Attr Key: padding-o-in +Clear Inherit +------------Attr Key: padding-a-in +Clear Inherit +------------Attr Key: padding-a-out +Clear Inherit +------------Attr Key: size-o +Main Constraint + | + ........(+)........ + | | +50.000000i .....(*)..... + | | + 2.000000i 3.000000i + +------------Attr Key: size-a +Min Constraint + | +45.000000i + +Main Constraint + | +40.000000i + +------------Attr Key: orientation +Property: adjacent-is-vertical +------------Attr Key: layout +Literal: "sierraGallery" +------------Attr Key: cell-names +--Literal List +blue +red +green +yellow +-----------------------------State Key: this.hovered +------------Attr Key: size-o +Main Constraint + | +20.000000i + +------------Attr Key: borderless +Property: disable +=================================Element======================================== +Path: i +------------------------------------------Body +-----------------------------State default +------------Attr Key: padding-o-out +Main Constraint + | + .....(+)...... + | | +3.000000i 300.000000i + +------------Attr Key: padding-o-in +Main Constraint + | + .....(+)...... + | | +3.000000i 300.000000i + +------------Attr Key: padding-a-in +Main Constraint + | + .....(+)...... + | | +3.000000i 300.000000i + +------------Attr Key: padding-a-out +Main Constraint + | + .....(+)...... + | | +3.000000i 300.000000i + +------------Attr Key: cell-names +--Literal List +blue +red +green +yellow +-----------------------------State Key: this.hovered +------------Attr Key: size-o +Clear Inherit +=================================Element======================================== +Path: n +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: x +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: anonymous-6-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: cell-names +--Literal List +blue +red +green +yellow +-----------------------------State Key: this.hovered +------------Attr Key: size-o +Main Constraint + | +20.000000i + +=================================Element======================================== +Path: anonymous-7-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: cell-names +--Literal List +blue +red +green +yellow +-----------------------------State Key: this.hovered +------------Attr Key: size-o +Main Constraint + | +20.000000i + diff --git a/src/tests/valid/ivdserial/attrInExpression.ivdserial b/src/tests/valid/ivdserial/attrInExpression.ivdserial new file mode 100644 index 0000000..279d55e --- /dev/null +++ b/src/tests/valid/ivdserial/attrInExpression.ivdserial @@ -0,0 +1,28 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: trans-o +Main Constraint + | +global::other.eleven + +------------Attr Key: trans-a +Main Constraint + | +global::other.size-o + +------------Attr Key: size-o +Main Constraint + | + .....(*)..... + | | +this.rel-x 4.000000i + +------------Attr Key: size-a +Main Constraint + | + .....(*)...... + | | +this.size-o 4.000000i + diff --git a/src/tests/valid/ivdserial/basic.ivdserial b/src/tests/valid/ivdserial/basic.ivdserial new file mode 100644 index 0000000..88c6378 --- /dev/null +++ b/src/tests/valid/ivdserial/basic.ivdserial @@ -0,0 +1,4 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/valid/ivdserial/booleaninheritance.ivdserial b/src/tests/valid/ivdserial/booleaninheritance.ivdserial new file mode 100644 index 0000000..6fa792a --- /dev/null +++ b/src/tests/valid/ivdserial/booleaninheritance.ivdserial @@ -0,0 +1,12 @@ +=================================Element======================================== +Path: anonymous-2-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: borderless +Property: enable +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: borderless +Property: disable diff --git a/src/tests/valid/ivdserial/cellalignmnet.ivdserial b/src/tests/valid/ivdserial/cellalignmnet.ivdserial new file mode 100644 index 0000000..23f150b --- /dev/null +++ b/src/tests/valid/ivdserial/cellalignmnet.ivdserial @@ -0,0 +1,32 @@ +=================================Element======================================== +Path: aCenter +------------------------------------------Body +-----------------------------State default +------------Attr Key: align-a +Property: align-center +------------Attr Key: align-o +Property: align-center +=================================Element======================================== +Path: left +------------------------------------------Body +-----------------------------State default +------------Attr Key: align-a +Property: align-inner +------------Attr Key: align-o +Property: align-inner +=================================Element======================================== +Path: right +------------------------------------------Body +-----------------------------State default +------------Attr Key: align-a +Property: align-outer +------------Attr Key: align-o +Property: align-outer +=================================Element======================================== +Path: anonymous-5-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: align-a +Property: align-center +------------Attr Key: align-o +Property: align-center diff --git a/src/tests/valid/ivdserial/cellnames.ivdserial b/src/tests/valid/ivdserial/cellnames.ivdserial new file mode 100644 index 0000000..e3b0f6c --- /dev/null +++ b/src/tests/valid/ivdserial/cellnames.ivdserial @@ -0,0 +1,19 @@ +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: cell-names +--Literal List +fuck +me +in +=================================Element======================================== +Path: anonymous-2-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: cell-names +--Literal List +oh +baby +its +triple diff --git a/src/tests/valid/ivdserial/classheaders.ivdserial b/src/tests/valid/ivdserial/classheaders.ivdserial new file mode 100644 index 0000000..df01d1e --- /dev/null +++ b/src/tests/valid/ivdserial/classheaders.ivdserial @@ -0,0 +1,13 @@ +=================================Element======================================== +Path: anonymous-2-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: anonymous-4-HORRIBLY-MAANGLED +Model fictionalModel +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/valid/ivdserial/classprecedence.ivdserial b/src/tests/valid/ivdserial/classprecedence.ivdserial new file mode 100644 index 0000000..d858408 --- /dev/null +++ b/src/tests/valid/ivdserial/classprecedence.ivdserial @@ -0,0 +1,12 @@ +=================================Element======================================== +Path: onetwo +------------------------------------------Body +-----------------------------State default +------------Attr Key: text +Literal: "This is class 2" +=================================Element======================================== +Path: twoone +------------------------------------------Body +-----------------------------State default +------------Attr Key: text +Literal: "This is class 1" diff --git a/src/tests/valid/ivdserial/classusingmodelsforinstance.ivdserial b/src/tests/valid/ivdserial/classusingmodelsforinstance.ivdserial new file mode 100644 index 0000000..5cb8b29 --- /dev/null +++ b/src/tests/valid/ivdserial/classusingmodelsforinstance.ivdserial @@ -0,0 +1,10 @@ +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +Model theModel +------------------------------------------Body +-----------------------------State default +------------Attr Key: size-a +Main Constraint + | +model.widthat + diff --git a/src/tests/valid/ivdserial/clearinherit.ivdserial b/src/tests/valid/ivdserial/clearinherit.ivdserial new file mode 100644 index 0000000..0826917 --- /dev/null +++ b/src/tests/valid/ivdserial/clearinherit.ivdserial @@ -0,0 +1,14 @@ +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: margin-a-in +Clear Inherit +------------Attr Key: margin-a-out +Clear Inherit +Main Constraint + | +1.000000i + +------------Attr Key: padding-a-in +Clear Inherit diff --git a/src/tests/valid/ivdserial/colorattr.ivdserial b/src/tests/valid/ivdserial/colorattr.ivdserial new file mode 100644 index 0000000..f522998 --- /dev/null +++ b/src/tests/valid/ivdserial/colorattr.ivdserial @@ -0,0 +1,10 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: color +Color: #7f0fff (127, 15, 255) +------------Attr Key: font-color +Color: #f26546 (242, 101, 70) +------------Attr Key: border-color +Color: #0fdd43 (15, 221, 67) diff --git a/src/tests/valid/ivdserial/colorliterals.ivdserial b/src/tests/valid/ivdserial/colorliterals.ivdserial new file mode 100644 index 0000000..afca3e2 --- /dev/null +++ b/src/tests/valid/ivdserial/colorliterals.ivdserial @@ -0,0 +1,30 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: color +Color: #0000ff (0, 0, 255) +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: color +Color: #6495ed (100, 149, 237) +=================================Element======================================== +Path: anonymous-2-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: color +Color: #dcdcdc (220, 220, 220) +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: color +Color: #00ff00 (0, 255, 0) +=================================Element======================================== +Path: anonymous-4-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: color +Color: #a020f0 (160, 32, 240) diff --git a/src/tests/valid/ivdserial/compoundstatekeys.ivdserial b/src/tests/valid/ivdserial/compoundstatekeys.ivdserial new file mode 100644 index 0000000..877d73d --- /dev/null +++ b/src/tests/valid/ivdserial/compoundstatekeys.ivdserial @@ -0,0 +1,17 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +-----------------------------State Expression: + | + .........and.......... + | | + .....or....... this.helpme + | | +this.effme this.duckme + +------------Attr Key: size-o +Main Constraint + | +42.000000i + diff --git a/src/tests/valid/ivdserial/constraintcase1.ivdserial b/src/tests/valid/ivdserial/constraintcase1.ivdserial new file mode 100644 index 0000000..f01a511 --- /dev/null +++ b/src/tests/valid/ivdserial/constraintcase1.ivdserial @@ -0,0 +1,124 @@ +=================================Element======================================== +Path: myBar +------------------------------------------Body +-----------------------------State default +--Declared Variable: offsetFactor + | + ......................(/)...................... + | | +[global::myBar::bar.trans-a] ..............(-).............. + | | + global::myBar::barArea.size-a global::myBar::bar.size-a + +--Declared Variable: scrollRatio + | + ..............(/)............... + | | +global::myBar::barArea.size-a [global::myBar::bar.size-a] + +------------Attr Key: layout +Property: vbox +------------Attr Key: cell-names +--Literal List +upperButter +middle +lowerButton +=================================Element======================================== +Path: myBar::barArea +------------------------------------------Body +-----------------------------State default +------------Attr Key: position-within +Single Key: global::myBar.middle +------------Attr Key: size-a +Main Constraint + | +100.000000% + +------------Attr Key: layout +Property: free-layout +=================================Element======================================== +Path: myBar::bar +------------------------------------------Body +-----------------------------State default +------------Attr Key: position-within +Single Key: global::myBar.barArea +------------Attr Key: trans-a +Starting Expression + | +0.000000i + +Min Constraint + | +0.000000i + +Max Constraint + | + ..........(-)........... + | | +global::myBar::barArea.size-a this.size-a + +------------Attr Key: size-a +Starting Expression + | +100.000000% + +Min Constraint + | +4.000000% + +Max Constraint + | +100.000000% + +=================================Element======================================== +Path: myView +------------------------------------------Body +-----------------------------State default +------------Attr Key: trans-a +Main Constraint + | + ..........(*)........... + | | +this.size-a [global::myBar.offsetFactor] + +-----------------------------State Key: this.geometry-updated +--Set Target: global::myBar.scrollRatio + | + ........(/)......... + | | +global::myPort.size-a this.size-a + +-----------------------------State Key: global::key.specialDownButtonThing +--Set Target: this.trans-a + | + ......(+)...... + | | +this.trans-a 20.000000i + +-----------------------------State Key: global::key.special-up-button-thing +--Set Target: this.trans-a + | + ......(-)...... + | | +this.trans-a 20.000000i + +=================================Element======================================== +Path: mySecondDependency +------------------------------------------Body +-----------------------------State default +------------Attr Key: trans-o +Main Constraint + | + ..........(*).......... + | | +this.size-o global::myBar.offsetFactor + +------------Attr Key: size-a +Main Constraint + | + ........(+)........ + | | + .....(/)..... 3.000000% + | | +10.000000u 2.000000i + diff --git a/src/tests/valid/ivdserial/declare.ivdserial b/src/tests/valid/ivdserial/declare.ivdserial new file mode 100644 index 0000000..be86e20 --- /dev/null +++ b/src/tests/valid/ivdserial/declare.ivdserial @@ -0,0 +1,15 @@ +=================================Element======================================== +Path: elem +Model aModel +------------------------------------------Body +-----------------------------State default +--Declared Variable: bloopy + | + ........(*)........ + | | +7.000000i [model::zooper.doop] + +--Declared Variable: zoopy + | +8.000000i + diff --git a/src/tests/valid/ivdserial/delay.ivdserial b/src/tests/valid/ivdserial/delay.ivdserial new file mode 100644 index 0000000..6e9331f --- /dev/null +++ b/src/tests/valid/ivdserial/delay.ivdserial @@ -0,0 +1,6 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: size-o +Delay: 10ms diff --git a/src/tests/valid/ivdserial/dimensions.ivdserial b/src/tests/valid/ivdserial/dimensions.ivdserial new file mode 100644 index 0000000..89cc8f5 --- /dev/null +++ b/src/tests/valid/ivdserial/dimensions.ivdserial @@ -0,0 +1,32 @@ +=================================Element======================================== +Path: anonymous-2-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: size-a +Main Constraint + | +10.000000i + +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: size-o +Main Constraint + | +10.000000i + +=================================Element======================================== +Path: anonymous-4-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: size-o +Main Constraint + | +10.000000i + +------------Attr Key: size-a +Main Constraint + | +10.000000i + diff --git a/src/tests/valid/ivdserial/expandcells.ivdserial b/src/tests/valid/ivdserial/expandcells.ivdserial new file mode 100644 index 0000000..8ef27f9 --- /dev/null +++ b/src/tests/valid/ivdserial/expandcells.ivdserial @@ -0,0 +1,16 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: greedy-a +Property: enable +------------Attr Key: greedy-o +Property: enable +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: greedy-a +Property: enable +------------Attr Key: greedy-o +Property: enable diff --git a/src/tests/valid/ivdserial/flatexpression.ivdserial b/src/tests/valid/ivdserial/flatexpression.ivdserial new file mode 100644 index 0000000..d35e4b7 --- /dev/null +++ b/src/tests/valid/ivdserial/flatexpression.ivdserial @@ -0,0 +1,24 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: padding-o-out +Main Constraint + | + ...............(-)................ + | | + ........(-)........ .....(/)..... + | | | | +4.000000i .....(*)..... 9.000000i 2.000000i + | | + 9.000000i 7.000000i + +------------Attr Key: padding-o-in +Main Constraint + | + ........(+)........ + | | +4.000000i .....(*)..... + | | + 9.000000i 7.000000i + diff --git a/src/tests/valid/ivdserial/inheritStateOverrideOrder.ivdserial b/src/tests/valid/ivdserial/inheritStateOverrideOrder.ivdserial new file mode 100644 index 0000000..eff5a57 --- /dev/null +++ b/src/tests/valid/ivdserial/inheritStateOverrideOrder.ivdserial @@ -0,0 +1,12 @@ +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: text +Literal: "Correct." +-----------------------------State Key: this.blep +------------Attr Key: title-text +Literal: "Corrrrrect." +-----------------------------State Key: this.hep +------------Attr Key: color +Color: #0000ff (0, 0, 255) diff --git a/src/tests/valid/ivdserial/inheritanceOverriding.ivdserial b/src/tests/valid/ivdserial/inheritanceOverriding.ivdserial new file mode 100644 index 0000000..8fd8c5e --- /dev/null +++ b/src/tests/valid/ivdserial/inheritanceOverriding.ivdserial @@ -0,0 +1,6 @@ +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: text +Literal: "Correct." diff --git a/src/tests/valid/ivdserial/inheritedvirtualstates.ivdserial b/src/tests/valid/ivdserial/inheritedvirtualstates.ivdserial new file mode 100644 index 0000000..72c3c84 --- /dev/null +++ b/src/tests/valid/ivdserial/inheritedvirtualstates.ivdserial @@ -0,0 +1,26 @@ +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +-----------------------------State Expression: + | + ....and.... + | | +this.x this.y + +------------Attr Key: color +Color: #00ff00 (0, 255, 0) +-----------------------------State Expression: + | + ....and.... + | | +this.x this.y + +------------Attr Key: color +Color: #0000ff (0, 0, 255) +-----------------------------State Expression: + | +not this.a + +------------Attr Key: text +Literal: "eyeyeyeyee" diff --git a/src/tests/valid/ivdserial/layout.ivdserial b/src/tests/valid/ivdserial/layout.ivdserial new file mode 100644 index 0000000..987e443 --- /dev/null +++ b/src/tests/valid/ivdserial/layout.ivdserial @@ -0,0 +1,24 @@ +=================================Element======================================== +Path: anonymous-4-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: layout +Property: standard +=================================Element======================================== +Path: anonymous-5-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: layout +Property: vbox +=================================Element======================================== +Path: anonymous-6-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: layout +Property: hbox +=================================Element======================================== +Path: anonymous-7-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: layout +Literal: "myCustomLayout" diff --git a/src/tests/valid/ivdserial/margin.ivdserial b/src/tests/valid/ivdserial/margin.ivdserial new file mode 100644 index 0000000..c6993bc --- /dev/null +++ b/src/tests/valid/ivdserial/margin.ivdserial @@ -0,0 +1,65 @@ +=================================Element======================================== +Path: elem +------------------------------------------Body +-----------------------------State default +------------Attr Key: margin-o-out +Main Constraint + | +42.000000i + +------------Attr Key: margin-o-in +Main Constraint + | +69.000000i + +------------Attr Key: margin-a-in +Main Constraint + | +34.000000i + +------------Attr Key: margin-a-out +Main Constraint + | +7.000000i + +=================================Element======================================== +Path: elem2 +------------------------------------------Body +-----------------------------State default +------------Attr Key: margin-o-in +Clear Inherit +------------Attr Key: margin-a-out +Main Constraint + | +1.000000i + +=================================Element======================================== +Path: elem3 +Model m +------------------------------------------Body +-----------------------------State default +------------Attr Key: margin-o-out +Main Constraint + | + .....(-)...... + | | +2.000000i [model.val] + +------------Attr Key: margin-a-in +Main Constraint + | +0.000000i + +------------Attr Key: margin-a-out +Main Constraint + | +1.000000i + +------------Attr Key: padding-o-out +Clear Inherit +------------Attr Key: padding-o-in +Clear Inherit +------------Attr Key: padding-a-in +Clear Inherit +------------Attr Key: padding-a-out +Clear Inherit diff --git a/src/tests/valid/ivdserial/modelaccesstext.ivdserial b/src/tests/valid/ivdserial/modelaccesstext.ivdserial new file mode 100644 index 0000000..5a35852 --- /dev/null +++ b/src/tests/valid/ivdserial/modelaccesstext.ivdserial @@ -0,0 +1,9 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +Model bod +------------------------------------------Body +-----------------------------State default +------------Attr Key: title-text +Single Key: model.f +------------Attr Key: text +Single Key: model::other.a diff --git a/src/tests/valid/ivdserial/modelheaders.ivdserial b/src/tests/valid/ivdserial/modelheaders.ivdserial new file mode 100644 index 0000000..0e26d92 --- /dev/null +++ b/src/tests/valid/ivdserial/modelheaders.ivdserial @@ -0,0 +1,15 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +Model whee +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +Model wheee +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: anonymous-2-HORRIBLY-MAANGLED +Model gee::lee::bee::free +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/valid/ivdserial/nestedexpression.ivdserial b/src/tests/valid/ivdserial/nestedexpression.ivdserial new file mode 100644 index 0000000..2ff6e9e --- /dev/null +++ b/src/tests/valid/ivdserial/nestedexpression.ivdserial @@ -0,0 +1,13 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: padding-o-in +Main Constraint + | + ........(*)........ + | | + .....(+)..... 7.000000i + | | +4.000000i 9.000000i + diff --git a/src/tests/valid/ivdserial/orientation.ivdserial b/src/tests/valid/ivdserial/orientation.ivdserial new file mode 100644 index 0000000..03e8226 --- /dev/null +++ b/src/tests/valid/ivdserial/orientation.ivdserial @@ -0,0 +1,12 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: orientation +Property: adjacent-is-horizontal +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: orientation +Property: adjacent-is-vertical diff --git a/src/tests/valid/ivdserial/positionwithin.ivdserial b/src/tests/valid/ivdserial/positionwithin.ivdserial new file mode 100644 index 0000000..c2d2c13 --- /dev/null +++ b/src/tests/valid/ivdserial/positionwithin.ivdserial @@ -0,0 +1,12 @@ +=================================Element======================================== +Path: anonymous-2-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: position-within +Single Key: global::elem +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: position-within +Single Key: global::elem::ace.cell diff --git a/src/tests/valid/ivdserial/precedence.ivdserial b/src/tests/valid/ivdserial/precedence.ivdserial new file mode 100644 index 0000000..63475a7 --- /dev/null +++ b/src/tests/valid/ivdserial/precedence.ivdserial @@ -0,0 +1,64 @@ +=================================Element======================================== +Path: whoopee +------------------------------------------Body +-----------------------------State default +------------Attr Key: margin-o-out +Main Constraint + | + ............(/)............. + | | + ............(/)............. 42.000000i + | | + ........(*)........ 2.000000i + | | +4.000000i .....(+)..... + | | + 2.000000i 9.000000i + +------------Attr Key: margin-o-in +Main Constraint + | + ............(/)............. + | | + ........(*)........ 2.000000i + | | +4.000000i .....(+)..... + | | + 2.000000i 9.000000i + +------------Attr Key: padding-o-out +Main Constraint + | + ........(+)........ + | | +4.000000i .....(*)..... + | | + 9.000000i 7.000000i + +------------Attr Key: padding-o-in +Main Constraint + | + ........(/)........ + | | + .....(*)..... 2.000000i + | | +4.000000i 2.000000i + +------------Attr Key: padding-a-in +Main Constraint + | + ........(+)........ + | | + .....(*)..... 2.000000i + | | +4.000000i 7.000000i + +------------Attr Key: padding-a-out +Main Constraint + | + ........(*)........ + | | +4.000000i .....(+)..... + | | + 7.000000i 2.000000i + diff --git a/src/tests/valid/ivdserial/property.ivdserial b/src/tests/valid/ivdserial/property.ivdserial new file mode 100644 index 0000000..6eb451d --- /dev/null +++ b/src/tests/valid/ivdserial/property.ivdserial @@ -0,0 +1,6 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: align-a +Property: align-center diff --git a/src/tests/valid/ivdserial/remoraOperatorInFreeElement.ivdserial b/src/tests/valid/ivdserial/remoraOperatorInFreeElement.ivdserial new file mode 100644 index 0000000..627f887 --- /dev/null +++ b/src/tests/valid/ivdserial/remoraOperatorInFreeElement.ivdserial @@ -0,0 +1,9 @@ +=================================Element======================================== +Path: anonymous-1-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: size-a +Main Constraint + | +global::anonymous-1-HORRIBLY-MAANGLED.widthat + diff --git a/src/tests/valid/ivdserial/remorabasic.ivdserial b/src/tests/valid/ivdserial/remorabasic.ivdserial new file mode 100644 index 0000000..e07c190 --- /dev/null +++ b/src/tests/valid/ivdserial/remorabasic.ivdserial @@ -0,0 +1,12 @@ +=================================Element======================================== +Path: remoraHostInstance +------------------------------------------Body +-----------------------------State default +------------Attr Key: text +Literal: "Host" +=================================Element======================================== +Path: remoraHostInstance::remora +------------------------------------------Body +-----------------------------State default +------------Attr Key: text +Literal: "Remora" diff --git a/src/tests/valid/ivdserial/remoracomplex.ivdserial b/src/tests/valid/ivdserial/remoracomplex.ivdserial new file mode 100644 index 0000000..498231c --- /dev/null +++ b/src/tests/valid/ivdserial/remoracomplex.ivdserial @@ -0,0 +1,12 @@ +=================================Element======================================== +Path: topInstance +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: topInstance::remora +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: topInstance::remora::remora2 +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/valid/ivdserial/remoradeep.ivdserial b/src/tests/valid/ivdserial/remoradeep.ivdserial new file mode 100644 index 0000000..86b07f2 --- /dev/null +++ b/src/tests/valid/ivdserial/remoradeep.ivdserial @@ -0,0 +1,24 @@ +=================================Element======================================== +Path: instance +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5 +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4 +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4::remora3 +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4::remora3::remora2 +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4::remora3::remora2::remora1 +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/valid/ivdserial/remoradeepmodels.ivdserial b/src/tests/valid/ivdserial/remoradeepmodels.ivdserial new file mode 100644 index 0000000..818dc2a --- /dev/null +++ b/src/tests/valid/ivdserial/remoradeepmodels.ivdserial @@ -0,0 +1,30 @@ +=================================Element======================================== +Path: instance +Model test +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5 +Model test +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4 +Model test +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4::remora3 +Model test::fewe +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4::remora3::remora2 +Model test::fewe +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4::remora3::remora2::remora1 +Model test::fewe +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/valid/ivdserial/remoradeeprootkey.ivdserial b/src/tests/valid/ivdserial/remoradeeprootkey.ivdserial new file mode 100644 index 0000000..3814aa3 --- /dev/null +++ b/src/tests/valid/ivdserial/remoradeeprootkey.ivdserial @@ -0,0 +1,28 @@ +=================================Element======================================== +Path: instance +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5 +------------------------------------------Body +-----------------------------State default +------------Attr Key: position-within +Single Key: global::instance +=================================Element======================================== +Path: instance::remora5::remora4 +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4::remora3 +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4::remora3::remora2 +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: instance::remora5::remora4::remora3::remora2::remora1 +------------------------------------------Body +-----------------------------State default +------------Attr Key: position-within +Single Key: global::instance::remora5::remora4::remora3::remora2 diff --git a/src/tests/valid/ivdserial/remoramodelheaders.ivdserial b/src/tests/valid/ivdserial/remoramodelheaders.ivdserial new file mode 100644 index 0000000..f981fcb --- /dev/null +++ b/src/tests/valid/ivdserial/remoramodelheaders.ivdserial @@ -0,0 +1,15 @@ +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED +Model test +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED::anonymous-1-HORRIBLY-MAANGLED +Model test::remSpot +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED::anonymous-2-HORRIBLY-MAANGLED +Model test +------------------------------------------Body +-----------------------------State default diff --git a/src/tests/valid/ivdserial/remorasandclassesohmy.ivdserial b/src/tests/valid/ivdserial/remorasandclassesohmy.ivdserial new file mode 100644 index 0000000..ae4ea00 --- /dev/null +++ b/src/tests/valid/ivdserial/remorasandclassesohmy.ivdserial @@ -0,0 +1,10 @@ +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED::anonymous-2-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: color +Color: #2234f5 (34, 52, 245) diff --git a/src/tests/valid/ivdserial/remoravaluekeysubstitution.ivdserial b/src/tests/valid/ivdserial/remoravaluekeysubstitution.ivdserial new file mode 100644 index 0000000..80c774a --- /dev/null +++ b/src/tests/valid/ivdserial/remoravaluekeysubstitution.ivdserial @@ -0,0 +1,29 @@ +=================================Element======================================== +Path: remoraHostInstance +Model muhModel +------------------------------------------Body +-----------------------------State default +--Declared Variable: halp + | + ..............(*)............... + | | +[global::remoraHostInstance::remora.dimen-adj] 314.000000i + +------------Attr Key: text +Literal: "Host" +=================================Element======================================== +Path: remoraHostInstance::remora +Model muhModel::whee +------------------------------------------Body +-----------------------------State default +------------Attr Key: position-within +Single Key: global::remoraHostInstance +------------Attr Key: text +Literal: "Remora" +------------Attr Key: size-a +Main Constraint + | + ...........(*)........... + | | +10.000000i global::remoraHostInstance.size-o + diff --git a/src/tests/valid/ivdserial/singlecomposite.ivdserial b/src/tests/valid/ivdserial/singlecomposite.ivdserial new file mode 100644 index 0000000..2d9ba8a --- /dev/null +++ b/src/tests/valid/ivdserial/singlecomposite.ivdserial @@ -0,0 +1,8 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +-----------------------------State Expression: + | +not this.blub + diff --git a/src/tests/valid/ivdserial/statemodifierInheritance.ivdserial b/src/tests/valid/ivdserial/statemodifierInheritance.ivdserial new file mode 100644 index 0000000..14c23c1 --- /dev/null +++ b/src/tests/valid/ivdserial/statemodifierInheritance.ivdserial @@ -0,0 +1,47 @@ +=================================Element======================================== +Path: isDerived +------------------------------------------Body +-----------------------------State default +------------Attr Key: induce-state +--Key List +this.frep +this.blep +this.beep +=================================Element======================================== +Path: isDerivedButCleared +------------------------------------------Body +-----------------------------State default +------------Attr Key: induce-state +Clear Inherit +=================================Element======================================== +Path: isDerivedButClearedAndAddedTo +------------------------------------------Body +-----------------------------State default +------------Attr Key: induce-state +Clear Inherit +--Key List +this.fack +=================================Element======================================== +Path: anonymous-4-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: bind-state +--Key List +global.beep +this.bdff +this.creep +=================================Element======================================== +Path: anonymous-5-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: toggle-state +--Key List +this.beep +this.overdidit +=================================Element======================================== +Path: ff +------------------------------------------Body +-----------------------------State default +------------Attr Key: unset-state +--Key List +this.beep diff --git a/src/tests/valid/ivdserial/stateorder.ivdserial b/src/tests/valid/ivdserial/stateorder.ivdserial new file mode 100644 index 0000000..0d8e797 --- /dev/null +++ b/src/tests/valid/ivdserial/stateorder.ivdserial @@ -0,0 +1,9 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +Model fack +------------------------------------------Body +-----------------------------State default +-----------------------------State Key: model.deee +-----------------------------State Key: this.beeee +-----------------------------State Key: this.ceeee +-----------------------------State Key: this.ayeeee diff --git a/src/tests/valid/ivdserial/text.ivdserial b/src/tests/valid/ivdserial/text.ivdserial new file mode 100644 index 0000000..d7896ae --- /dev/null +++ b/src/tests/valid/ivdserial/text.ivdserial @@ -0,0 +1,12 @@ +=================================Element======================================== +Path: anonymous-2-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: text +Literal: " jk text" +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +------------Attr Key: title-text +Literal: "myTitle" diff --git a/src/tests/valid/ivdserial/triggers.ivdserial b/src/tests/valid/ivdserial/triggers.ivdserial new file mode 100644 index 0000000..ad0ffb2 --- /dev/null +++ b/src/tests/valid/ivdserial/triggers.ivdserial @@ -0,0 +1,30 @@ +=================================Element======================================== +Path: lordy +Model oof +------------------------------------------Body +-----------------------------State default +------------Attr Key: trigger +--Key List +model.doitnow +this.eff +this.blep +model.notnow +model.effie +=================================Element======================================== +Path: anonymous-3-HORRIBLY-MAANGLED +Model mod +------------------------------------------Body +-----------------------------State default +------------Attr Key: trigger +--Key List +model.corn +model.elm +-----------------------------State Key: this.what +------------Attr Key: trigger +--Key List +model.witch +model.bitch +-----------------------------State Key: this.wwww +------------Attr Key: trigger +--Key List +this.f diff --git a/src/tests/valid/ivdserial/virtualstatekeys.ivdserial b/src/tests/valid/ivdserial/virtualstatekeys.ivdserial new file mode 100644 index 0000000..a98c05e --- /dev/null +++ b/src/tests/valid/ivdserial/virtualstatekeys.ivdserial @@ -0,0 +1,82 @@ +=================================Element======================================== +Path: anonymous-0-HORRIBLY-MAANGLED +------------------------------------------Body +-----------------------------State default +-----------------------------State Expression: + | + ....and.... + | | +global.f this.s + +-----------------------------State Expression: + | + ........or......... + | | + ......or........ this.i + | | + ....or..... this.y + | | +this.f this.s + +-----------------------------State Expression: + | + ......and....... + | | + ....or..... this.i + | | +this.f this.s + +-----------------------------State Expression: + | + ......or........ + | | +this.i ....and.... + | | + this.s this.i + +-----------------------------State Expression: + | + ....or...... + | | +not this.i this.s + +-----------------------------State Expression: + | + .......xor....... + | | +this.i .....and..... + | | + this.s not this.i + +-----------------------------State Expression: + | + ........or......... + | | +this.i .......and....... + | | + this.s .....or....... + | | + this.f not global.f + +-----------------------------State Expression: + | + ........or......... + | | +this.i .....not and..... + | | + this.s .....or....... + | | + this.f not global.f + +-----------------------------State Expression: + | + ....and.... + | | +global.f this.s + +-----------------------------State Expression: + | + .......and....... + | | +global::otherleem.f this.s + diff --git a/src/tests/valid/layout.ivd b/src/tests/valid/layout.ivd new file mode 100755 index 0000000..1e77276 --- /dev/null +++ b/src/tests/valid/layout.ivd @@ -0,0 +1,28 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.astandard +{ + layout: standard; +} + +.avbox +{ + layout: vbox; +} + +.ahbox +{ + layout: hbox; +} + +.acustom +{ + layout: myCustomLayout; +} + +# : astandard; +# : avbox; +# : ahbox; +# : acustom; diff --git a/src/tests/valid/margin.ivd b/src/tests/valid/margin.ivd new file mode 100755 index 0000000..e67fb25 --- /dev/null +++ b/src/tests/valid/margin.ivd @@ -0,0 +1,19 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#elem +{ + margin: 42, 69, 34, 7; +} + +#elem2 +{ + margin: pass, clear, pass, 1; +} + +#elem3 -> m +{ + padding: clear; + margin: 2 - [model.val], pass, 0, 1; +} diff --git a/src/tests/valid/modelaccesstext.ivd b/src/tests/valid/modelaccesstext.ivd new file mode 100644 index 0000000..c801c2f --- /dev/null +++ b/src/tests/valid/modelaccesstext.ivd @@ -0,0 +1,9 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> bod +{ + text: model::other.a; + title-text: model.f; +} diff --git a/src/tests/valid/modelheaders.ivd b/src/tests/valid/modelheaders.ivd new file mode 100644 index 0000000..477c808 --- /dev/null +++ b/src/tests/valid/modelheaders.ivd @@ -0,0 +1,9 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> ::whee; +# -> wheee; +# -> gee::lee::bee::free; + +//... I mean I guess that covers it. diff --git a/src/tests/valid/nestedexpression.ivd b/src/tests/valid/nestedexpression.ivd new file mode 100644 index 0000000..e82f939 --- /dev/null +++ b/src/tests/valid/nestedexpression.ivd @@ -0,0 +1,8 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + padding-o-in: (4 + 9) * 7; +} diff --git a/src/tests/valid/orientation.ivd b/src/tests/valid/orientation.ivd new file mode 100644 index 0000000..3fcf2fa --- /dev/null +++ b/src/tests/valid/orientation.ivd @@ -0,0 +1,12 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + orientation: adjacent-is-horizontal; +} +# +{ + orientation: adjacent-is-vertical; +} diff --git a/src/tests/valid/positionwithin.ivd b/src/tests/valid/positionwithin.ivd new file mode 100755 index 0000000..a259b0e --- /dev/null +++ b/src/tests/valid/positionwithin.ivd @@ -0,0 +1,16 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.elemOnly +{ + position-within: elem; +} + +.elemCell +{ + position-within: elem::ace.cell; +} + +# : elemOnly; +# : elemCell; diff --git a/src/tests/valid/precedence.ivd b/src/tests/valid/precedence.ivd new file mode 100644 index 0000000..224c6e8 --- /dev/null +++ b/src/tests/valid/precedence.ivd @@ -0,0 +1,14 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +#whoopee +{ + margin-o-in: (4 * (2 + 9)) / (2); + margin-o-out: 4 * (2 + 9) / 2 / 42; + padding-o-in: (4 + (9 * 7)); + padding-o-out: 4 + 9 * 7; + padding-a-in: 4 * 7 + 2; + padding-a-out: 4 * (7 + 2); + padding-o-in: (4 * 2) / 2; +} diff --git a/src/tests/valid/property.ivd b/src/tests/valid/property.ivd new file mode 100755 index 0000000..32de214 --- /dev/null +++ b/src/tests/valid/property.ivd @@ -0,0 +1,8 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ + align-x: align-center; +} diff --git a/src/tests/valid/remoraOperatorInFreeElement.ivd b/src/tests/valid/remoraOperatorInFreeElement.ivd new file mode 100755 index 0000000..ef2905c --- /dev/null +++ b/src/tests/valid/remoraOperatorInFreeElement.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.a1class1 +{ + width: @.widthat; //shit shit shit models can have this names fuck fuck +} + +# : a1class1; diff --git a/src/tests/valid/remorabasic.ivd b/src/tests/valid/remorabasic.ivd new file mode 100755 index 0000000..d4bc668 --- /dev/null +++ b/src/tests/valid/remorabasic.ivd @@ -0,0 +1,15 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.host +{ + text: "Host"; +} + +@host.remora +{ + text: "Remora"; +} + +#remoraHostInstance : host; diff --git a/src/tests/valid/remoracomplex.ivd b/src/tests/valid/remoracomplex.ivd new file mode 100755 index 0000000..b3bd432 --- /dev/null +++ b/src/tests/valid/remoracomplex.ivd @@ -0,0 +1,11 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.top; +.child; +@child.remora2; +@top.remora : child; +#topInstance : top; + +//Should be three instances? diff --git a/src/tests/valid/remoradeep.ivd b/src/tests/valid/remoradeep.ivd new file mode 100755 index 0000000..88d61c7 --- /dev/null +++ b/src/tests/valid/remoradeep.ivd @@ -0,0 +1,22 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.level1; +@level1.remora1; + +.level2; +@level2.remora2 : level1; + +.level3; +@level3.remora3 : level2; + +.level4; +@level4.remora4 : level3; + +.level5; +@level5.remora5 : level4; + +#instance : level5; + +//Should be 6 I think. diff --git a/src/tests/valid/remoradeepmodels.ivd b/src/tests/valid/remoradeepmodels.ivd new file mode 100755 index 0000000..80a3527 --- /dev/null +++ b/src/tests/valid/remoradeepmodels.ivd @@ -0,0 +1,22 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.level1; +@level1.remora1 -> @; + +.level2; +@level2.remora2 -> @ : level1; + +.level3; +@level3.remora3 -> @::fewe : level2; + +.level4; +@level4.remora4 -> @ : level3; + +.level5; +@level5.remora5 -> @ : level4; + +#instance -> test : level5; + +//Should be 6 I think. diff --git a/src/tests/valid/remoradeeprootkey.ivd b/src/tests/valid/remoradeeprootkey.ivd new file mode 100755 index 0000000..1458fe6 --- /dev/null +++ b/src/tests/valid/remoradeeprootkey.ivd @@ -0,0 +1,28 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.level1; +@level1.remora1 +{ + position-within: @; +} + +.level2; +@level2.remora2 : level1; + +.level3; +@level3.remora3 : level2; + +.level4; +@level4.remora4 : level3; + +.level5; +@level5.remora5 : level4 +{ + position-within: @; +} + +#instance : level5; + +//Should be 6 I think. diff --git a/src/tests/valid/remoramodelheaders.ivd b/src/tests/valid/remoramodelheaders.ivd new file mode 100644 index 0000000..8a9fcb4 --- /dev/null +++ b/src/tests/valid/remoramodelheaders.ivd @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.base; + +@base -> @::remSpot; +@base -> @; + +# -> test : base; diff --git a/src/tests/valid/remorasandclassesohmy.ivd b/src/tests/valid/remorasandclassesohmy.ivd new file mode 100644 index 0000000..338cc7f --- /dev/null +++ b/src/tests/valid/remorasandclassesohmy.ivd @@ -0,0 +1,14 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.host; + +.base +{ + color: #2234f5; +} + +@host : base; + +# : host; diff --git a/src/tests/valid/remoravaluekeysubstitution.ivd b/src/tests/valid/remoravaluekeysubstitution.ivd new file mode 100755 index 0000000..11a826f --- /dev/null +++ b/src/tests/valid/remoravaluekeysubstitution.ivd @@ -0,0 +1,18 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.host +{ + declare expression: halp = [@::remora.dimen-adj] * 314; + text: "Host"; +} + +@host.remora -> @::whee +{ + position-within: @; + text: "Remora"; + size-x: 10 * @.height; +} + +#remoraHostInstance -> muhModel : host; diff --git a/src/tests/valid/singlecomposite.ivd b/src/tests/valid/singlecomposite.ivd new file mode 100644 index 0000000..a95fc4e --- /dev/null +++ b/src/tests/valid/singlecomposite.ivd @@ -0,0 +1,8 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ +state !blub: +} diff --git a/src/tests/valid/statemodifierInheritance.ivd b/src/tests/valid/statemodifierInheritance.ivd new file mode 100755 index 0000000..62bd84c --- /dev/null +++ b/src/tests/valid/statemodifierInheritance.ivd @@ -0,0 +1,46 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.is +{ + induce-state:blep; + induce-state: beep ; +} + +#isDerived : is +{ + induce-state:frep ; +} + +#isDerivedButCleared : is +{ + induce-state: clear; +} + +#isDerivedButClearedAndAddedTo : is +{ + induce-state: fack; + induce-state: clear; +} + +# +{ + bind-state: ::.beep , bdff, this.creep + +; +} + +# +{ + toggle-state: beep; + toggle-state: overdidit; +} + +#ff +{ + unset-state: beep; +} + +//The idea is that you want them to compound unless otherwise noted. So, clear +// if you don't want to inherit. diff --git a/src/tests/valid/stateorder.ivd b/src/tests/valid/stateorder.ivd new file mode 100644 index 0000000..f0713ee --- /dev/null +++ b/src/tests/valid/stateorder.ivd @@ -0,0 +1,11 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# -> fack +{ +state model.deee: +state beeee: +state ceeee: +state ayeeee: +} diff --git a/src/tests/valid/text.ivd b/src/tests/valid/text.ivd new file mode 100755 index 0000000..a89a02c --- /dev/null +++ b/src/tests/valid/text.ivd @@ -0,0 +1,16 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.atext +{ + text: " jk text"; +} + +.atitleText +{ + title-text: "myTitle"; +} + +# : atext; +# : atitleText; diff --git a/src/tests/valid/triggers.ivd b/src/tests/valid/triggers.ivd new file mode 100755 index 0000000..c8b51d7 --- /dev/null +++ b/src/tests/valid/triggers.ivd @@ -0,0 +1,38 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +.base +{ + trigger: eff; + trigger: this.blep; + trigger: model.notnow , model.effie; +} + +#lordy -> oof: base +{ + trigger: model.doitnow; +} + +//These were originally intended to merge within the same element... +// but the local overwriting seems more idiomatic. + +.rebase +{ + trigger: model.elm; + +state this.what: + trigger: model.bitch; +} + +# -> mod : rebase +{ + trigger: model.corn; +state this.what: + trigger: model.witch; + +state wwww: + trigger: f; +} + +//I should really think about this... diff --git a/src/tests/valid/virtualstatekeys.ivd b/src/tests/valid/virtualstatekeys.ivd new file mode 100755 index 0000000..98bf2c4 --- /dev/null +++ b/src/tests/valid/virtualstatekeys.ivd @@ -0,0 +1,17 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +spec bleeding; + +# +{ +state ::.f & s: +state f | s | y | i: +state (f | s) & i: +state i | (s & i): +state !i | s: +state i xor (s & !i): +state i | (s & (f | !::.f)): +state i | !(s & (f | !::.f)): +state ::.f & s: +state ::otherleem.f & s: +} diff --git a/src/text.cpp b/src/text.cpp new file mode 100644 index 0000000..9a13f12 --- /dev/null +++ b/src/text.cpp @@ -0,0 +1,23 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "text.h" + +namespace IVD +{ +namespace Text +{ + +SplitStringPair extractExcess(DisplayItem* style, const std::string text, const int space) +{ + SplitStringPair result; + + const int stringLength = getMaxStringLengthForSpace(style, text, space); + + result.remaining = text.substr(0, stringLength); + result.overflowing = text.substr(stringLength); + + return result; +} + +}//Text +}//IVD diff --git a/src/text.h b/src/text.h new file mode 100644 index 0000000..6b7ebe8 --- /dev/null +++ b/src/text.h @@ -0,0 +1,35 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef TEXT_H +#define TEXT_H + +#include + +#include "defaults.h" +#include "geometry.h" + +namespace IVD +{ + +class Canvas; + +namespace Text +{ + +//The following are defined in the corresponding textdriver source file. +Dimens getRunDimensions(DisplayItem* item, const std::string text); +int getMaxStringLengthForSpace(DisplayItem* style, const std::string text, const int space); + +//These are defined in text.cpp, as they rely on the generics above. +struct SplitStringPair +{ + std::string remaining; + std::string overflowing; +}; + +SplitStringPair extractExcess(DisplayItem* style, const std::string text, const int space); + +}//Text +}//IVD + +#endif // TEXT_H diff --git a/src/user_include/IVD_c.h b/src/user_include/IVD_c.h new file mode 100644 index 0000000..10bd0a9 --- /dev/null +++ b/src/user_include/IVD_c.h @@ -0,0 +1,172 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef IVD_ALPHA_C_BINDINGS_H +#define IVD_ALPHA_C_BINDINGS_H + +#ifdef __cpluplus +#error IVD_c.h is a C header only, wrap your include in an extern block or use IVD_cpp.h instead. +#endif + +#include "IVD_status.h" + +#define IVD_FILL_PRECEDENCE_GREEDY 0 +#define IVD_FILL_PRECEDENCE_SHRINKY 1 + +#define IVD_USER_ATTRIBUTE_TYPE_STRING 3001 +#define IVD_USER_ATTRIBUTE_TYPE_SCALAR 3002 +#define IVD_USER_ATTRIBUTE_TYPE_TOKEN 3003 + +struct IVD_Environment; + +struct IVD_Widget; +//register IVD_Widget as layout to be used with layout attribute + +struct IVD_Dimens; +struct IVD_Coords; +struct IVD_Rect; +struct IVD_GeometryProposal; +struct IVD_Style; +struct IVD_Element; +struct IVD_Canvas; + +//----------------------------------------------------------------------------------Function pointer typedefs +typedef void(*IVD_user_data_destructor)(void*); + +typedef double(*IVD_callback_get_number)(const char*, void*); +typedef const char*(*IVD_callback_get_string)(const char*, void*); + +//0 false 1 true +typedef int(*IVD_callback_check_const)(const char* key, void*); + +typedef void(*IVD_callback_set_number)(const char* key, double, void*); +typedef void(*IVD_callback_set_string)(const char* key, const char* val, void*); + +typedef void(*IVD_callback_trigger)(const char*, void*); + +//------------------------------------------------------------------------------------------------Environment +IVD_Environment* IVD_create_environment(); +void IVD_destroy_environment(IVD_Environment*); + +int IVD_environment_load_file(IVD_Environment*, const char* path); +const char* IVD_environment_get_compiler_errors(IVD_Environment*); +void IVD_environment_run(IVD_Environment*); + +void IVD_environment_register_widget(IVD_Environment *environment, const char* name, + IVD_Widget* (*ctor)(IVD_Environment*), + void (*dtor)(IVD_Widget*), + int (*getFillPrecedence)(IVD_Widget*, const int), + void (*shape)(IVD_Widget *, IVD_GeometryProposal *), + void (*draw)(IVD_Widget*, IVD_Canvas*), + IVD_Dimens* (*getSpace)(IVD_Widget *), //canbe null + int (*detectCollisionPoint)(IVD_Widget *, IVD_Coords *), //canbe null + void (*distributeCollisionPoint)(IVD_Widget*), + void (*triggerHandler)(IVD_Widget*, const char*)); + +//IVD manages widget lifetimes so they can be "deleted later" +IVD_Widget* IVD_environment_widget_create(IVD_Environment*, const char* name, IVD_Widget* parent); +IVD_Element* IVD_environment_create_element_from_class(IVD_Environment*, const char* className, IVD_Widget* parent); +void IVD_environment_widget_destroy(IVD_Environment*, IVD_Widget*); +void IVD_environment_element_destroy(IVD_Environment*, IVD_Widget* parent, IVD_Element*); //Seems like this could do some real damage... + + +void IVD_environment_register_layout(IVD_Environment* environment, + const char* name, + IVD_Widget* (*ctor)(IVD_Environment*), + void (*dtor)(IVD_Widget*), + int (*getFillPrecedence)(IVD_Widget*, const int), + void (*shape)(IVD_Widget*, IVD_GeometryProposal*), + void (*draw)(IVD_Widget*, IVD_Canvas*), + IVD_Dimens* (*getSpace)(IVD_Widget *), + void (*bubbler)(IVD_Widget*)); + +void IVD_compiler_register_attribute(IVD_Environment*, + const char* attribute); + +void IVD_compiler_add_attribute_type(IVD_Environment*, + const char* attribute, + int attrType); + +void IVD_compiler_register_property(IVD_Environment*, + const char*); + +void IVD_compiler_register_property_valid(IVD_Environment*, + const char* property, + const char* value); + + +//More importantly... +const char* IVD_element_read_attribute_string(IVD_Environment*, + const char* attribute); + +//This would hypothetically compute the expression... If we still +// wanna have expressions in IVD. +double IVD_element_read_attribute_expression(IVD_Environment*, + const char* attribute); + + +//----------------------------------------------------------------------------------------------Dust Bindings +IVD_Dimens* IVD_dimens_alloc(); +void IVD_dimens_free(IVD_Dimens* space); +int* IVD_dimens_w(IVD_Dimens* space); +int* IVD_dimens_h(IVD_Dimens* space); + +IVD_Coords* IVD_coords_alloc(); +void IVD_coords_free(IVD_Coords* point); +int* IVD_coords_x(IVD_Coords* point); +int* IVD_coords_y(IVD_Coords* point); + +IVD_Rect* IVD_rect_alloc(); +void IVD_rect_free(IVD_Rect* rect); +IVD_Dimens* IVD_rect_get_dimens(IVD_Rect* rect); //Still owned by *rect +IVD_Coords* IVD_rect_get_coords(IVD_Rect* rect); +void IVD_rect_set_space(IVD_Rect* rect, IVD_Dimens* space); //Copies values +void IVD_rect_set_point(IVD_Rect* rect, IVD_Coords* point); + +//-------------------------------------------------------------------------------------------GeometryProposal +IVD_GeometryProposal* IVD_geoprop_alloc(); +IVD_GeometryProposal* IVD_geoprop_alloc_copy(IVD_GeometryProposal*); +void IVD_geoprop_free(IVD_GeometryProposal* prop); +IVD_Dimens* IVD_geoprop_proposed_space(IVD_GeometryProposal* prop); +int* IVD_geoprop_expand_horizontal(IVD_GeometryProposal* prop); +int* IVD_geoprop_expand_vertical(IVD_GeometryProposal* prop); +int* IVD_geoprop_shrink_horizontal(IVD_GeometryProposal* prop); +int* IVD_geoprop_shrink_vertical(IVD_GeometryProposal* prop); +int IVD_geoprop_verify_compliance(IVD_GeometryProposal* prop, IVD_Dimens* space); +void IVD_geoprop_round_conflicts(IVD_GeometryProposal* prop, IVD_Dimens* space); + + +//----------------------------------------------------------------------------------------------------Element +IVD_Dimens* IVD_element_get_dimens(const IVD_Element*); +int IVD_element_get_fill_precedence(IVD_Element*, int angle); //Angle -> FillPrecedence + +void IVD_element_shape(IVD_Element*, IVD_GeometryProposal*); +void IVD_element_set_offset(IVD_Element*, IVD_Coords*); + +void IVD_element_draw(IVD_Element*); +void IVD_element_bubble(IVD_Element*); + +IVD_Element* IVD_widget_get_underlying_element(IVD_Environment*, IVD_Widget*); + +void IVD_widget_get_child_elements(IVD_Environment*, IVD_Widget*, IVD_Element*** result, int* size); +IVD_Element* IVD_widget_get_child_element_for_named_cell(IVD_Environment* environment, IVD_Widget*, const char* name); + +//void IVD_draw_X(IVD_Canvas*, ...); //canvas cursor is already set to the correct offset. + +void IVD_canvas_draw_image(IVD_Canvas*, + int x, + int y, + int width, + int height, + int stride, + int channels, + unsigned char* data); + +//------------------------------------------------------------------------------------------------------Style +IVD_Style* IVD_create_style(const IVD_Environment* theEnv, const char* className); +void IVD_destroy_style(const IVD_Environment* theEnv, IVD_Style* style); + +void IVD_style_set_state(IVD_Style* style, const char* stateKey, const int state); +//Should we even be able to readback the state? + +//That's all, folks! >:3c +#endif //IVD_ALPHA_C_BINDINGS_H diff --git a/src/user_include/IVD_constants_c.h b/src/user_include/IVD_constants_c.h new file mode 100644 index 0000000..0ea4944 --- /dev/null +++ b/src/user_include/IVD_constants_c.h @@ -0,0 +1,20 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + + +#ifndef IVD_CONSTANTS_C +#define IVD_CONSTANTS_C + +//-----------------------------------GEOMETRY CONSTANTS +#define IVD_ANGLE_HORIZONTAL 0 +#define IVD_ANGLE_VERTICAL 1 + +#define IVD_ANGLE_ADJACENT 0 +#define IVD_ANGLE_OPPOSITE 1 + +#define IVD_FILL_PRECEDENCE_GREEDY 0 +#define IVD_FILL_PRECEDENCE_SHRINKY 1 + +//-----------------------------------COMPILER CONSTANTS + + +#endif //IVD_CONSTANTS_C diff --git a/src/user_include/IVD_status.h b/src/user_include/IVD_status.h new file mode 100644 index 0000000..9468237 --- /dev/null +++ b/src/user_include/IVD_status.h @@ -0,0 +1,10 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef IVD_STATUS_H +#define IVD_STATUS_H + +#define IVD_STATUS_SUCCESS 0 //tfw the status s u c c +#define IVD_STATUS_FILE_NOT_FOUND 1 +#define IVD_STATUS_COMPILE_ERROR 2 + +#endif//IVD_STATUS_H diff --git a/src/user_include/cpp/IVD_cpp.h b/src/user_include/cpp/IVD_cpp.h new file mode 100644 index 0000000..d0b0a65 --- /dev/null +++ b/src/user_include/cpp/IVD_cpp.h @@ -0,0 +1,365 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include +#include +#include +#include +#include + +#include "IVD_geometry_proposal.h" + +namespace IVD +{ + +namespace bindings +{ + + +class UserWidget; +class UserLayout; + +namespace Internals //reference at your own peril +{ + +inline IVD_Widget* cast(UserLayout* widget) +{ return reinterpret_cast(widget); } + +inline IVD_Widget* cast(UserWidget* widget) +{ return reinterpret_cast(widget); } + +inline UserWidget* cast(IVD_Widget* widget) +{ return reinterpret_cast(widget); } + + +}//Internals + +class Canvas; +class Element +{ + IVD_Element* elem; + +public: + Element(IVD_Element* elem): elem(elem) {} + + IVD_Element* getUnderlyinggggg() + { return elem; } + + void render() + { IVD_element_draw(elem); } + + void set_offset(const Coords offset) + { + std::unique_ptr + managedCoords(IVD_coords_alloc(), IVD_coords_free); + *IVD_coords_x(managedCoords.get()) = offset.x; + *IVD_coords_y(managedCoords.get()) = offset.y; + IVD_element_set_offset(elem, managedCoords.get()); + } + + void bubble() + { IVD_element_bubble(elem); } + + FillPrecedence get_fill_precedence(const Angle theAngle) + { + return static_cast + (IVD_element_get_fill_precedence(elem, static_cast(theAngle))); + } + + Dimens get_dimens() + { return IVD_element_get_dimens(elem); } + + void shape(GeometryProposal prop) + { + auto smartProp = prop.createSmartPointerProp(); + IVD_element_shape(elem, smartProp.get()); + } +}; + +class UserLayout +{ + friend class Environment; + IVD_Environment* myEnv = nullptr; + +protected: + Dimens myDimens; + + void destroy_child_element(Element elem) + { + IVD_environment_element_destroy(myEnv, + Internals::cast(this), + elem.getUnderlyinggggg()); + } + + void render_children_unordered() + { + applyToChildren([&](Element child) + { child.render(); }); + } + + void bubble_children_unordered() + { + applyToChildren([&](Element child) + { child.bubble(); }); + } + + FillPrecedence any_greedy_children_for_angle(const Angle theAngle) + { + auto result = FillPrecedence::Shrinky; + applyToChildren([&](Element child) + { + if(child.get_fill_precedence(theAngle) == FillPrecedence::Greedy) + result = FillPrecedence::Greedy; + }); + + return result; + } + + void applyToChildren(std::function fun) + { + IVD_Element** childArray = nullptr; + int childArraySize = 0; + + IVD_widget_get_child_elements(myEnv, + Internals::cast(this), + &childArray, + &childArraySize); + + for(int i = 0; i != childArraySize; ++i) + fun(Element(childArray[i])); + } + + UserLayout* getChildForNamedCell(const std::string name) + { + return reinterpret_cast + (IVD_widget_get_child_element_for_named_cell(myEnv, + Internals::cast(this), + name.c_str())); + } + +public: + UserLayout(IVD_Environment* myEnv): myEnv(myEnv) {} + virtual ~UserLayout() {} + Dimens get_content_dimens() { return myDimens; } + + virtual FillPrecedence get_fill_precedence(const Angle angle) = 0; + virtual void shape(const GeometryProposal officialProposal) = 0; + + //canvas will be null for layouts; + virtual void draw(Canvas theCanvas) = 0; + + //This just defines collision order + //Call "process_collision_point_for_child + // for all children + virtual void bubble_children() {} +}; + +class UserWidget : public UserLayout +{ +public: + virtual ~UserWidget() {} + + virtual bool detect_collision_point(const Coords point) { return false; } + virtual void trigger(const std::string trig) {} +}; + +template +class ManagedUserWidget +{ + friend class Environment; //So we can have a protected constructor + std::function destroyLaterBinding; + + T* myWigee; + + ManagedUserWidget(T* theWidget, + std::function dtorBinding): + myWigee(theWidget), + destroyLaterBinding(dtorBinding) + {} + +public: + ~ManagedUserWidget() + { destroyLaterBinding(); } + + T* get() + { return myWigee; } +}; + +class Canvas +{ + //Just a thin wrapper, it's owned deep down inside the core of + // IVD + IVD_Canvas* internalCanvas; + +public: + Canvas(IVD_Canvas* internalCanvas): + internalCanvas(internalCanvas) + {} + + + virtual void drawBitmapRGBoptionalA(Coords dest, + int width, + int height, + int stride, + int channels, + unsigned char* data) + { + IVD_canvas_draw_image(internalCanvas, + dest.x, + dest.y, + width, + height, + stride, + channels, + data); + } + + //Image and font are the only things that aren't handled by + // the regular thangs +}; + + +namespace Internals +{ + +inline void userWidgetDestructorHook(IVD_Widget* widget) +{ delete cast(widget); } + +inline int userWidgetGetFillPrecedenceHook(IVD_Widget* widget, const int angle) +{ + const auto inputAngle = static_cast(angle); + return static_cast(cast(widget)->get_fill_precedence(inputAngle)); +} + +inline void userWidgetShapeHook(IVD_Widget* widget, IVD_GeometryProposal* prop) +{ cast(widget)->shape(GeometryProposal(prop)); } + +inline void userWidgetDrawHook(IVD_Widget* widget, IVD_Canvas* canvas) +{ cast(widget)->draw(Canvas(canvas)); } + +inline IVD_Dimens* userWidgetGetDimensHook(IVD_Widget* widget) +{ + const Dimens dimens = cast(widget)->get_content_dimens(); + //Alloc AFTER in case getDimens throws, although it should be in a try block here anyway... + // TODO XXX + + IVD_Dimens* result = IVD_dimens_alloc(); + *IVD_dimens_h(result) = dimens.h; + *IVD_dimens_w(result) = dimens.w; + return result; +} + +inline void userWidgetBubble(IVD_Widget* widget) +{ cast(widget)->bubble_children(); } +inline int userWidgetDetectCollisionCoordsHook(IVD_Widget* widget, IVD_Coords* coords) +{ return cast(widget)->detect_collision_point(Coords(coords)); } + +inline void userWidgetTriggerHook(IVD_Widget* widget, const char* trig) +{ cast(widget)->trigger(trig); } + +template +T* generic_factory(IVD_Environment* theEnv) +{ + return new T(theEnv); +} + +}//Internals + +//ohhhhhhhhhhhhhhhhhhhhhh +//I rmeember thisssssssssss +//It's why I called the public interface into Environment "runtime" +// before... + +class Environment +{ + std::unique_ptr internal; + +public: + Environment(): internal(IVD_create_environment(), &IVD_destroy_environment) {} + + template + void register_widget(const std::string name) + { + T* (*factory)(IVD_Environment*) = Internals::generic_factory; //WE CAN DO THAT??? + + //---->VOODOO<---- + auto castFactory = reinterpret_cast(factory); + //---->VOODOO<---- + + IVD_environment_register_widget(internal.get(), + name.c_str(), + castFactory, + Internals::userWidgetDestructorHook, + Internals::userWidgetGetFillPrecedenceHook, + Internals::userWidgetShapeHook, + Internals::userWidgetDrawHook, + Internals::userWidgetGetDimensHook, + Internals::userWidgetDetectCollisionCoordsHook, + Internals::userWidgetBubble, + Internals::userWidgetTriggerHook); + } + + template + void register_layout(const std::string name) + { + T* (*factory)(IVD_Environment*) = Internals::generic_factory; + //---->VOODOO<---- + auto castFactory = reinterpret_cast(factory); + //---->VOODOO<---- + + IVD_environment_register_layout(internal.get(), + name.c_str(), + castFactory, + Internals::userWidgetDestructorHook, + Internals::userWidgetGetFillPrecedenceHook, + Internals::userWidgetShapeHook, + Internals::userWidgetDrawHook, + Internals::userWidgetGetDimensHook, + Internals::userWidgetBubble); + } + + template + T* create_unmanaged_user_widget(const std::string name, UserLayout* parent = nullptr) + { + IVD_Widget* widget = IVD_environment_widget_create(internal.get(), + name.c_str(), + reinterpret_cast(parent)); + auto result = reinterpret_cast(widget); + result->myEnv = internal.get(); + return result; + } + + + template + ManagedUserWidget create_managed_user_widget(const std::string name, UserLayout* parent = nullptr) + { + T* unmanaged = create_unmanaged_user_widget(name, parent); + + std::function dtor = [&] + { IVD_environment_widget_destroy(internal.get(), unmanaged); }; + + return ManagedUserWidget(unmanaged, dtor); + } + + Element create_unmanaged_widget_from_class(const std::string className, UserLayout* parent = nullptr) + { + return IVD_environment_create_element_from_class(internal.get(), + className.c_str(), + reinterpret_cast(parent)); + } + + int load_IVD_from_file(const std::string& path) + { return IVD_environment_load_file(internal.get(), path.c_str()); } + + std::string get_compiler_errors() + { return std::string(IVD_environment_get_compiler_errors(internal.get())); } + + void run() + { IVD_environment_run(internal.get()); } +}; + + + +}//bindings +}//IVD diff --git a/src/user_include/cpp/IVD_geometry.h b/src/user_include/cpp/IVD_geometry.h new file mode 100644 index 0000000..19fe62e --- /dev/null +++ b/src/user_include/cpp/IVD_geometry.h @@ -0,0 +1,298 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +//THIS FILE MUST REMAIN SAFE TO BE INCLUDED AS A HEADER +// ONLY USER INCLUDE!!!! + +#include +#include +#include + +#include "../IVD_constants_c.h" + +extern "C" +{ +#include "../IVD_c.h" +} + +namespace IVD +{ + +//This might all look like pedantic gold-plating, OO-snorting +// idealogical-cargo-culting... But the resulting layout +// code is a helluva lot easier to grok with when these +// concepts are broken up like this. Trust me, I've done both. + +enum class Angle +{ + Horizontal = IVD_ANGLE_HORIZONTAL, + Vertical = IVD_ANGLE_VERTICAL, + + Adjacent = IVD_ANGLE_ADJACENT, + Opposite = IVD_ANGLE_OPPOSITE, +}; + + +//Greedy simply means that the layout will never take less than +// it is offered. +//Shrinky means that it will take less if it can get away with it. +//But either setting will still ask for more space if not given enough. +enum class FillPrecedence +{ + Greedy = IVD_FILL_PRECEDENCE_GREEDY, + Shrinky = IVD_FILL_PRECEDENCE_SHRINKY, +}; + +struct Dimens; + +struct Coords +{ + int x; + int y; + + Coords(): x(0), y(0) {} + Coords(int x, int y): x(x), y(y) {} + Coords(const Dimens& theDimens); + Coords(IVD_Coords* other) + { + x = *IVD_coords_x(other); + y = *IVD_coords_y(other); + } + + friend Coords operator+(Coords left, const Coords& right) + { + left.x += right.x; + left.y += right.y; + + return left; + } + + Coords operator+=(const Coords& other) + { + x += other.x; + y += other.y; + return *this; + } + + int& get(Angle angle) + { + if(angle == Angle::Horizontal) return x; + return y; + + } + int get(Angle angle) const + { + if(angle == Angle::Horizontal) return x; + return y; + } + + Coords operator-(const Coords& other) const + { return Coords(x - other.x, y - other.y); } + + Coords operator-=(const Coords& other) + { + x -= other.x; + y -= other.y; + return *this; + } + + std::string generatePrintout() const; + + bool operator==(const Coords& other) const + { return std::tie(x, y) == std::tie(other.x, other.y); } + + bool operator!=(const Coords& other) const + { return !(*this == other); } + + bool operator<(const Coords& other) const + { return std::tie(x, y) < std::tie(other.x, other.y); } + +}; + +struct Dimens +{ + int w; + int h; + + Dimens(): w(0), h(0) {} + Dimens(int width, int height): w(width), h(height) {} + Dimens(const Coords theCoords) + { + w = theCoords.x; + h = theCoords.y; + } + Dimens(IVD_Dimens* other) + { + w = *IVD_dimens_w(other); + h = *IVD_dimens_h(other); + } + + friend Dimens operator+(Dimens left, const Dimens& right) + { + left.w += right.w; + left.h += right.h; + + return left; + } + + friend Dimens operator+=(Dimens& left, const Dimens& right) + { + left.w += right.w; + left.h += right.h; + return left; //Back, and to the left. + } + + friend Dimens operator-(Dimens left, const Dimens& right) + { + left.w -= right.w; + left.h -= right.h; + + return left; + } + + friend Dimens operator-=(Dimens& left, const Dimens& right) + { + left.w -= right.w; + left.h -= right.h; + return left; + } + + friend Coords operator+(Coords left, const Dimens& right) + { + left.x += right.w; + left.y += right.h; + + return left; + } + + int& get(Angle angle) + { + if(angle == Angle::Horizontal) + return w; + return h; + } + + const int& get(Angle angle) const + { + if(angle == Angle::Horizontal) + return w; + return h; + } + + std::string generatePrintout() const; + + bool operator==(const Dimens& other) const + { return std::tie(w, h) == std::tie(other.w, other.h); } + + bool operator!=(const Dimens& other) const + { return !(*this == other); } + + bool operator<(const Dimens& other) const + { return std::tie(w, h) < std::tie(other.w, other.h); } +}; + +inline Coords::Coords(const Dimens& theDimens) +{ + x = theDimens.w; + y = theDimens.h; +} + +struct Rect +{ + Coords c; + Dimens d; + + Rect() {} + Rect(const Coords theCoords, const Dimens theDimens): c(theCoords), d(theDimens) {} + Rect(IVD_Rect* other) + { + c = IVD_rect_get_coords(other); + d = IVD_rect_get_dimens(other); + } + + bool checkCollision(const Coords point) const + { + if(point.x >= c.x && + point.x < c.x + d.w && + point.y >= c.y && + point.y < c.y + d.h) + { return true; } + + return false; + } + + bool checkCollision(const Rect box) const + { + return c.x < box.c.x + box.d.w && + c.y < box.c.y + box.d.w && + box.c.x < c.x + d.w && + box.c.y < c.y + d.h; + } + + const Rect& operator+(const Coords& other); + + std::string generatePrintout() const; + + bool operator==(const Rect& other) const + { return std::tie(c, d) == std::tie(other.c, other.d); } + + bool operator!=(const Rect& other) const + { return !(*this == other); } + + bool operator<(const Rect& other) const + { return std::tie(c, d) < std::tie(other.c, other.d); } +}; + + +template +T getForAngle(T horizontal, T vertical, Angle theAngle) +{ + if(theAngle == Angle::Horizontal) + return horizontal; + return vertical; +} + +template +std::string streamPrintoutHelper(const T& val) +{ + std::stringstream ss; + ss << val.generatePrintout(); + return ss.str(); +} + + +inline std::ostream& operator<<(std::ostream& theStream, const Dimens theDimens) +{ + theStream << "w " << theDimens.w << ", h " << theDimens.h; + return theStream; +} + +inline std::string Dimens::generatePrintout() const +{ return streamPrintoutHelper(*this); } + +inline std::ostream& operator<<(std::ostream& theStream, const Coords theCoords) +{ + theStream << "x " << theCoords.x << ", y " << theCoords.y; + return theStream; +} + +inline std::string Coords::generatePrintout() const +{ return streamPrintoutHelper(*this); } + + +inline std::ostream& operator<<(std::ostream& theStream, const Rect theRect) +{ + theStream << theRect.d << ", " << theRect.c; + return theStream; +} + +inline std::string Rect::generatePrintout() const +{ return streamPrintoutHelper(*this); } + +//Ashamed of this one so it's pretty far down. +inline int zeroGuard(const int i) +{ return (i < 0) ? 0 : i; } + + +}//IVD diff --git a/src/user_include/cpp/IVD_geometry_proposal.h b/src/user_include/cpp/IVD_geometry_proposal.h new file mode 100644 index 0000000..f119309 --- /dev/null +++ b/src/user_include/cpp/IVD_geometry_proposal.h @@ -0,0 +1,119 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include +#include "IVD_geometry.h" + +namespace IVD +{ + +struct GeometryProposal +{ +private: + int allowedExpandHorizontal; + int allowedExpandVertical; + int allowedShrinkHorizontal; + int allowedShrinkVertical; + +public: + GeometryProposal(): + allowedExpandHorizontal(false), + allowedExpandVertical(false), + allowedShrinkHorizontal(false), + allowedShrinkVertical(false) + {} + GeometryProposal(IVD_GeometryProposal* other) + { + allowedExpandHorizontal = *IVD_geoprop_expand_horizontal(other); + allowedExpandVertical = *IVD_geoprop_expand_vertical(other); + allowedShrinkHorizontal = *IVD_geoprop_shrink_horizontal(other); + allowedShrinkVertical = *IVD_geoprop_shrink_vertical(other); + + proposedDimensions = IVD_geoprop_proposed_space(other); + } + + std::unique_ptr createSmartPointerProp() + { + auto managedProp = std::unique_ptr + (IVD_geoprop_alloc(), IVD_geoprop_free); + + auto propPointer = managedProp.get(); + *IVD_geoprop_expand_horizontal(propPointer) = allowedExpandHorizontal; + *IVD_geoprop_expand_vertical(propPointer) = allowedExpandVertical; + + *IVD_geoprop_shrink_horizontal(propPointer) = allowedShrinkHorizontal; + *IVD_geoprop_shrink_vertical(propPointer) = allowedShrinkVertical; + + IVD_Dimens* proposed = IVD_geoprop_proposed_space(propPointer); + *IVD_dimens_h(proposed) = proposedDimensions.h; + *IVD_dimens_w(proposed) = proposedDimensions.w; + + return std::move(managedProp); + } + + Dimens proposedDimensions; + + int& expandForAngle(Angle angel) + { + return angel == Angle::Horizontal ? allowedExpandHorizontal + : allowedExpandVertical; + } + + const bool expandForAngleConst(Angle angel) const + { + return angel == Angle::Horizontal ? allowedExpandHorizontal + : allowedExpandVertical; + } + + int& shrinkForAngle(Angle angel) + { + return angel == Angle::Horizontal ? allowedShrinkHorizontal + : allowedShrinkVertical; + } + + const bool shrinkForAngleConst(Angle angel) const + { + return angel == Angle::Horizontal ? allowedShrinkHorizontal + : allowedShrinkVertical; + } + + bool verifyCompliance(const Dimens& region) const + { + //Oh I'm a fucking moron + if(!allowedExpandHorizontal && region.w > proposedDimensions.w) + return false; + if(!allowedExpandVertical && region.h > proposedDimensions.h) + return false; + if(!allowedShrinkHorizontal && region.w < proposedDimensions.w) + return false; + if(!allowedShrinkVertical && region.h < proposedDimensions.h) + return false; + + return true; + } + + //Round cell size against constraints (Really only from an overconstrained condition, + // whereby the proposal and revised proposal are in conflict) + Dimens roundConflicts(Dimens usedSpace) const + { + const int propCellAdj = proposedDimensions.get(Angle::Horizontal); + const int propCellOpp = proposedDimensions.get(Angle::Vertical); + + if((!expandForAngleConst(Angle::Horizontal) && usedSpace.get(Angle::Horizontal) > propCellAdj) || + (!shrinkForAngleConst(Angle::Horizontal) && usedSpace.get(Angle::Horizontal) < propCellAdj)) + { + usedSpace.get(Angle::Horizontal) = propCellAdj; + } + + if((!expandForAngleConst(Angle::Vertical) && usedSpace.get(Angle::Vertical) > propCellOpp) || + (!shrinkForAngleConst(Angle::Vertical) && usedSpace.get(Angle::Vertical) < propCellOpp)) + { + usedSpace.get(Angle::Vertical) = propCellOpp; + } + + return usedSpace; + } +}; + +} diff --git a/src/valuekey.cpp b/src/valuekey.cpp new file mode 100644 index 0000000..3b04f1d --- /dev/null +++ b/src/valuekey.cpp @@ -0,0 +1,78 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "valuekey.h" +#include "expression.h" + +#include + +#include "rustutils/routine.h" + +#include "assert.h" + +namespace IVD +{ + +ScopedValueKey::ScopedValueKey() {} +ScopedValueKey::ScopedValueKey(Scope theScope): myScope(theScope) {} +ScopedValueKey::~ScopedValueKey() +{ /*Just need that complete type for Expression?*/ } + +ScopedValueKey::ScopedValueKey(const ScopedValueKey& other): + myScope(other.myScope), + path(other.path), + key(other.key), + parameter(other.parameter ? std::unique_ptr(new Expression(*other.parameter)) + : nullptr), //Good lord + definedAt(other.definedAt) +{} + +ScopedValueKey& ScopedValueKey::operator=(const ScopedValueKey& other) +{ + myScope = other.myScope; + path = other.path; + key = other.key; + + if(other.parameter) parameter = std::unique_ptr(new Expression(*other.parameter)); + + definedAt = other.definedAt; + + return *this; +} + +void ScopedValueKey::apply(std::function fun) +{ + fun(*this); + if(parameter) parameter->applyToEachScopedValueKey(fun); +} + +void ScopedValueKey::merge(const ScopedValueKey& right) +{ + if(!path && right.path) + path = right.path; + else if(path && right.path) + RustUtils::Routine::appendContainer(*path, *right.path); +} + +std::string ScopedValueKey::generatePrintout() const +{ + std::stringstream ss; + if(myScope == IVD::ScopedValueKey::Scope::Element) ss << "this"; + if(myScope == IVD::ScopedValueKey::Scope::Model) ss << "model"; + if(myScope == IVD::ScopedValueKey::Scope::RemorialClass) ss << "@"; + if(myScope == IVD::ScopedValueKey::Scope::Material) ss << "material"; + if(myScope == IVD::ScopedValueKey::Scope::Global) ss << "global"; + + if(path) ss << "::" << *path; + if(key) ss << "." << *key; + if(parameter) + { + //"material" is always printed here. + ss << ", with parameter expression:" << std::endl + << parameter.get()->generatePrintout(); + } + + return ss.str(); +} + + +}//IVD diff --git a/src/valuekey.h b/src/valuekey.h new file mode 100644 index 0000000..1db5b92 --- /dev/null +++ b/src/valuekey.h @@ -0,0 +1,92 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#ifndef VALUEKEY_H +#define VALUEKEY_H + +#include "rustutils/lexcompare.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "codeposition.h" + +namespace IVD +{ + +typedef std::string ValueKey; +typedef std::string ModelKey; +typedef std::vector ValueKeyPath; + +class Expression; + +struct ScopedValueKey +{ + enum class Scope + { + Global, + Element, + Model, + Material, + + RemorialClass, //This should only show up in the compiler //Is this still true? + }; + std::optional myScope; + std::optional path; + std::optional key; + std::unique_ptr parameter; + + //I dare you to rename this. + CodePosition definedAt; + + ScopedValueKey(); + ScopedValueKey(Scope theScope); + ~ScopedValueKey(); + + ScopedValueKey(const ScopedValueKey& other); + + ScopedValueKey& operator=(const ScopedValueKey& other); + + void setScopeIfUnset(const Scope theScope) + { if(!myScope) myScope = theScope; } + + bool empty() + { return !myScope && !path && !key && !parameter; } + + void addToPath(const ValueKey pathpiece) + { + if(path) path->push_back(pathpiece); + else path = {pathpiece}; + } + + void setKey(const ValueKey theKey) + { key = theKey; } + + void apply(std::function fun); + + RUSTUTILS_DEFINE_COMP(ScopedValueKey, myScope, path, key) + + //Why does this function exist? + void merge(const ScopedValueKey& right); + + std::string generatePrintout() const; +}; + +inline std::ostream& operator<<(std::ostream& theStream, ValueKeyPath path) +{ + for(auto it = path.begin(); it != path.end(); ++it) + { + theStream << *it; + if(it + 1 != path.end()) theStream << "::"; + } + + return theStream; +} + +}//IVD + +#endif // VALUEKEY_H diff --git a/src/virtualstatekey.cpp b/src/virtualstatekey.cpp new file mode 100644 index 0000000..6b2e82d --- /dev/null +++ b/src/virtualstatekey.cpp @@ -0,0 +1,157 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "virtualstatekey.h" +#include "environment.h" +#include "statemanager.h" +#include "keywords.h" +#include "binaryexpressionprinter.h" + +namespace IVD +{ + + +VirtualStateKey::Node::Node(const VirtualStateKey::Node& other): + operation(other.operation), + negation(other.negation), + stateKey(other.stateKey) +{ + if(other.left) left = std::make_unique(*other.left); + if(other.right) right = std::make_unique(*other.right); +} + +VirtualStateKey::Node& VirtualStateKey::Node::operator=(const VirtualStateKey::Node& other) +{ + operation = other.operation; + negation = other.negation; + stateKey = other.stateKey; + + if(other.left) left = std::make_unique(*other.left); + if(other.right) right = std::make_unique(*other.right); + + return *this; +} + +VirtualStateKeyPrecursor::Node::Node(const VirtualStateKeyPrecursor::Node& other): + operation(other.operation), + negation(other.negation), + stateKeyPrecursor(other.stateKeyPrecursor) +{ + if(other.left) left = std::make_unique(*other.left); + if(other.right) right = std::make_unique(*other.right); +} + +void VirtualStateKey::Node::addYourStateKeys(std::vector* keys) +{ + keys->push_back(stateKey); + if(left) left->addYourStateKeys(keys); + if(right) right->addYourStateKeys(keys); +} + +bool VirtualStateKey::Node::evaluate(StateManager* stateManager) const +{ + if(!left && !right) + { + if(negation) return !stateManager->checkState(stateKey); + else return stateManager->checkState(stateKey); + } + + assert(left && right); + + switch(operation) + { + case Keyword::Or: return left->evaluate(stateManager) || right->evaluate(stateManager); + case Keyword::And: return left->evaluate(stateManager) && right->evaluate(stateManager); + case Keyword::Xor: return left->evaluate(stateManager) != right->evaluate(stateManager); + default: assert(false); + } +} + +VirtualStateKeyPrecursor::Node& VirtualStateKeyPrecursor::Node::operator=(const VirtualStateKeyPrecursor::Node& other) +{ + operation = other.operation; + negation = other.negation; + stateKeyPrecursor = other.stateKeyPrecursor; + + if(other.left) left = std::make_unique(*other.left); + if(other.right) right = std::make_unique(*other.right); + + return *this; +} + +std::string VirtualStateKeyPrecursor::Node::printoutThySelf() const +{ + std::string literal; + if(negation) literal += "not "; + + switch(operation) + { + case Keyword::ScopedValueKey: + literal += stateKeyPrecursor.generatePrintout(); + break; + case Keyword::And: + literal += "and"; + break; + case Keyword::Or: + literal += "or"; + break; + case Keyword::Xor: + literal += "xor"; + break; + + default: assert(false); + } + + return literal; +} + +std::vector VirtualStateKey::getAffectedKeys() +{ + std::vector keys; + root.addYourStateKeys(&keys); + return keys; +} + +void VirtualStateKey::syncProxyState(StateManager* stateManager) const +{ + stateManager->mutateIfObserved(proxyStateKey, root.evaluate(stateManager)); +} + +VirtualStateKey VirtualStateKeyPrecursor::generateVirtualStateKey(Environment* env) +{ + VirtualStateKey myVirtual; + myVirtual.proxyStateKey = env->generateStateKeyFromPrecursor(proxyStateKeyPrecursor, context); + populateNode(&root, &myVirtual.root, env); + return myVirtual; +} + +std::string VirtualStateKeyPrecursor::generatePrintout() +{ + return generateExpressionPrintout(root); +} + +void VirtualStateKeyPrecursor::populateNode(VirtualStateKeyPrecursor::Node* pos, + VirtualStateKey::Node* otherNode, + Environment* env) +{ + otherNode->operation = pos->operation; + otherNode->negation = pos->negation; + + //Not every node is a state... + if(pos->stateKeyPrecursor.key) otherNode->stateKey = env->generateStateKeyFromPrecursor(pos->stateKeyPrecursor, context); + + if(pos->left) + { + if(otherNode->left) otherNode->left.reset(); + otherNode->left = std::make_unique(); + populateNode(pos->left.get(), otherNode->left.get(), env); + } + if(pos->right) + { + if(otherNode->right) otherNode->right.reset(); + otherNode->right = std::make_unique(); + populateNode(pos->right.get(), otherNode->right.get(), env); + } +} + + +}//IVD diff --git a/src/virtualstatekey.h b/src/virtualstatekey.h new file mode 100644 index 0000000..87ba411 --- /dev/null +++ b/src/virtualstatekey.h @@ -0,0 +1,93 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include "assert.h" +#include +#include +#include + +#include "statekey.h" + +namespace IVD +{ + +class Environment; +class StateManager; + +struct VirtualStateKey +{ + StateKey proxyStateKey; + + struct Node + { + int operation; + bool negation; + std::unique_ptr left; + std::unique_ptr right; + + StateKey stateKey; + + + Node() {} + Node(const Node& other); + Node& operator=(const Node& other); + + void addYourStateKeys(std::vector* keys); + bool evaluate(StateManager* stateManager) const; + + }; + + Node root; + + std::vector getAffectedKeys(); + void syncProxyState(StateManager* stateManager) const; + + bool operator<(const VirtualStateKey& other) const + { return proxyStateKey < other.proxyStateKey; } + + bool operator==(const VirtualStateKey& other) const + { return !(*this < other) && !(other < *this); } + + bool operator!=(const VirtualStateKey& other) const + { return !(*this == other); } +}; + +struct VirtualStateKeyPrecursor +{ + //Must have element scope and no path. + ScopedValueKey proxyStateKeyPrecursor; + DisplayItem* context; + + struct Node + { + int operation; + bool negation; + std::unique_ptr left; + std::unique_ptr right; + + ScopedValueKey stateKeyPrecursor; + + Node(): negation(false) {} + Node(const Node& other); + Node& operator=(const Node& other); + + std::string printoutThySelf() const; + }; + + Node root; + + bool checkIsVirtual() + { return !root.left && !root.right && !root.negation; } + + VirtualStateKey generateVirtualStateKey(Environment* env); + + std::string generatePrintout(); + +private: + void populateNode(VirtualStateKeyPrecursor::Node* pos, + VirtualStateKey::Node* otherNode, Environment* env); +}; + + +}//IVD diff --git a/src/widget.h b/src/widget.h new file mode 100644 index 0000000..8271ce8 --- /dev/null +++ b/src/widget.h @@ -0,0 +1,116 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +extern "C" +{ +#include "user_include/IVD_c.h" +} + +#include +#include "geometry.h" +#include "geometryproposal.h" + +namespace IVD +{ + +class Canvas; +class Environment; + + +//Layouts are just a specialized type of widget + +struct WidgetBlueprints +{ + //Layout/widget uses these + std::string name; + bool isWidget; + IVD_Widget* (*ctor)(IVD_Environment*) = nullptr; + void (*dtor)(IVD_Widget*) = nullptr; + int (*getFillPrecedence)(IVD_Widget*, const int) = nullptr; + void (*shape)(IVD_Widget*, IVD_GeometryProposal*) = nullptr; + IVD_Dimens* (*getSpace)(IVD_Widget*); //Widgets only know about drawing area + void (*draw)(IVD_Widget*, IVD_Canvas*) = nullptr; //canbe null + void (*bubbler)(IVD_Widget*) = nullptr; + + //Widget specific + int (*detectCollisionPoint)(IVD_Widget*, IVD_Coords*) = nullptr; //canbe null + void (*triggerHandler)(IVD_Widget*, const char*) = nullptr; +}; + +class WidgetWrapper +{ + typedef std::unique_ptr SmartWidgetPointer; + WidgetBlueprints myBlueprints; + SmartWidgetPointer underlyingWidget; + + bool isSet = false; + +public: + WidgetWrapper(): underlyingWidget(nullptr, nullptr) {} + WidgetWrapper(Environment* theEnv, const WidgetBlueprints blueprints): + myBlueprints(blueprints), + underlyingWidget(blueprints.ctor(reinterpret_cast(theEnv)), blueprints.dtor), + isSet(true) + {} + + void reset(Environment* theEnv, const WidgetBlueprints blueprints) + { + underlyingWidget.reset(); + underlyingWidget = SmartWidgetPointer(blueprints.ctor(reinterpret_cast(theEnv)), blueprints.dtor); + myBlueprints = blueprints; + } + + bool checkIsSet() + { return bool(underlyingWidget); } + + bool isDrawable() + { return myBlueprints.draw; } + + bool isLayout() + { return !myBlueprints.isWidget; } + + void destroy() + { underlyingWidget.reset(); } + + IVD_Widget* get() + { return underlyingWidget.get(); } + + FillPrecedence getFillPrecedence(Angle theAngel) + { + const auto prec = myBlueprints.getFillPrecedence(get(), + getForAngle(0, 1, theAngel)); + return prec == 0 ? FillPrecedence::Greedy + : FillPrecedence::Shrinky; + } + + Dimens getSpace() + { + IVD_Dimens* space = myBlueprints.getSpace(underlyingWidget.get()); + Dimens result = *reinterpret_cast(space); + IVD_dimens_free(space); + return result; + } + + void shape(GeometryProposal prop) + { myBlueprints.shape(get(), reinterpret_cast(&prop)); } + + void draw(Canvas* canvas) + { myBlueprints.draw(get(), reinterpret_cast(canvas)); } + + void bubble() + { return myBlueprints.bubbler(get()); } + + bool detectCollisionPoint(Coords point) + { + if(!myBlueprints.detectCollisionPoint) + return false; + return myBlueprints.detectCollisionPoint(get(), reinterpret_cast(&point)); + } + + void handleTrigger(const std::string triggerName) + { myBlueprints.triggerHandler(get(), triggerName.c_str()); } +}; + + +}//IVD diff --git a/src/widgets/boxlayout.cpp b/src/widgets/boxlayout.cpp new file mode 100644 index 0000000..4feeb80 --- /dev/null +++ b/src/widgets/boxlayout.cpp @@ -0,0 +1,253 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "boxlayout.h" + +#include "assert.h" + +namespace IVD +{ +namespace std_widgets +{ + +void BoxLayout::shape(const GeometryProposal officialProposal) +{ + std::vector theMaterials; + + applyToChildren([&](bindings::Element child) + { theMaterials.push_back(child); }); + + const int CellCount = theMaterials.size(); + const Angle Opposite = (Adjacent == Angle::Horizontal) ? Angle::Vertical + : Angle::Horizontal; + + const int AvailableAdjacentSpace = officialProposal.proposedDimensions.get(Adjacent); + const int AvailableOppositeSpace = officialProposal.proposedDimensions.get(Opposite); + + std::vector greedy; + std::vector shrinky; + + for(bindings::Element material : theMaterials) + { + const FillPrecedence Pref = material.get_fill_precedence(Adjacent); + if(Pref == FillPrecedence::Greedy) + greedy.push_back(material); + if(Pref == FillPrecedence::Shrinky) + shrinky.push_back(material); + } + + int usedAdjacentSpace; + int usedOppositeSpace; + + auto resetWorkingAdjacent = [&] + { usedAdjacentSpace = 0; }; + + auto resetWorkingOpposite = [&] + { usedOppositeSpace = 0; }; + + auto resetWorkingDimensions = [&] + { + resetWorkingAdjacent(); + resetWorkingOpposite(); + }; + + resetWorkingDimensions(); + + bool invalidOpposite = false; + + auto applyAdjacentFormulaToChild = [&](bindings::Element material, + GeometryProposal childProposal, + std::function adjacentFormula) + { + //Please leave this here I'm sick of adding it back for debugging + const auto proposedAdjacentSize = adjacentFormula(material); + + //----------Adjacent + childProposal.proposedDimensions.get(Adjacent) = proposedAdjacentSize; + + material.shape(childProposal); + const Dimens childDimens = material.get_dimens(); + + usedAdjacentSpace += childDimens.get(Adjacent); + + //----------Opposite + const int UsedOppositeThisRound = childDimens.get(Opposite); + + if(UsedOppositeThisRound > usedOppositeSpace) + usedOppositeSpace = UsedOppositeThisRound; + + if(AvailableOppositeSpace != UsedOppositeThisRound) + invalidOpposite = true; + + //---------- + assert(childProposal.verifyCompliance(childDimens)); + }; + + auto applyToSet = [&](const std::vector& items, + GeometryProposal childProposal, + std::function formula) + { + for(bindings::Element material : items) + applyAdjacentFormulaToChild(material, childProposal, formula); + }; + + auto adjustOpposite = [&]() + { + if(!invalidOpposite) return; + + //The problem here is that one widget might have actually used all of the opposite + // correctly, while a different one shrank. So really we need a better test to see + // if the widgets didn't all use the same space!!! : TODO, is this still a thing??? + // I don't understand if it's still a problem, doesn't seem like it??? + resetWorkingAdjacent(); + + //We don't discriminate between greedy and shrinky this time, because + // we're only looking to update the child opposites and perhaps + // shrink our adjacent. + for(bindings::Element material : theMaterials) + { + //We've already determined the opposite, so lock both opposite shrink and expand. + //If the opposite is larger, we might recover some adjacent space, + // so we don't shrink lock that dimension. + + //If the opposite is smaller, do we need to lock adjacent shrinking? + //I can't think of anything but the most contrived scenario where + // the adjacent would shrink further. But I also don't see why we + // *have to* lock it... + //(Wouldn't it shrink if we increase the opposite in a vertical text flow scenario?) + //Yeah... Lock it. + + //The computed adjacent is always big enough to handle the computed opposite + // or larger. We won't be suggesting a smaller opposite, so the adjacent should + // not expand further. + + GeometryProposal childProposal = officialProposal; + childProposal.proposedDimensions = material.get_dimens(); + childProposal.proposedDimensions.get(Opposite) = usedOppositeSpace; + + childProposal.expandForAngle(Opposite) = false; + childProposal.shrinkForAngle(Opposite) = false; + childProposal.expandForAngle(Adjacent) = false; + childProposal.shrinkForAngle(Adjacent) = false; + + material.shape(childProposal); + const Dimens childDimens = material.get_dimens(); + usedAdjacentSpace += childDimens.get(Adjacent); + + assert(childProposal.verifyCompliance(childDimens)); + } + + invalidOpposite = false; + }; + + //Initial pass, get an idea of what the natural sizes are + { + //No explicit expand for opposite because it's taking in the + // higher up suggestion which we much obey anyway. + //Otherwise it throws off the overall opposite rule. + //If it's locked it can't legally grow!!!! Or shrink... No shrinky! + // extra space goes to the child cell anyway. + GeometryProposal childProposal = officialProposal; + childProposal.expandForAngle(Adjacent) = true; + childProposal.shrinkForAngle(Adjacent) = true; + + auto initalCellSizeFormula = [=](bindings::Element) -> int + { return zeroGuard((AvailableAdjacentSpace - usedAdjacentSpace) / CellCount); }; + + applyToSet(shrinky, childProposal, initalCellSizeFormula); + applyToSet(greedy, childProposal, initalCellSizeFormula); + adjustOpposite(); + } + + + //If the sizes are bad, then we adjust + if(usedAdjacentSpace > officialProposal.proposedDimensions.get(Adjacent)) + { + if(!officialProposal.expandForAngleConst(Adjacent)) + { + //No overflow mode, because if we can't expand, we can't expand. + //So it must go to the children. The default is for the whole thing + // to take on the full size, if you want a viewport, put this layout + // inside an item with a viewport layout. + + const int cutSize = (usedAdjacentSpace - AvailableAdjacentSpace) / CellCount; + + auto adjustingSizeFormula = [&](bindings::Element material) -> int + { return zeroGuard(material.get_dimens().get(Adjacent) - cutSize); }; + + //(╯°□°)╯︵ ┻━┻ + resetWorkingDimensions(); + + GeometryProposal childProposal = officialProposal; + childProposal.expandForAngle(Adjacent) = false; + + applyToSet(shrinky, childProposal, adjustingSizeFormula); + applyToSet(greedy, childProposal, adjustingSizeFormula); + adjustOpposite(); + }//Else is just whatev's + } + + if(usedAdjacentSpace < officialProposal.proposedDimensions.get(Adjacent)) + { + if(!officialProposal.shrinkForAngleConst(Adjacent) && CellCount) + { + //By default we only expand greedy. But if there is no greedy and we MUST expand... + //Actually not sure if this is ever greedy, now that I thonk abut it? Can it be? TODO + auto& theSet = greedy.size() ? greedy + : shrinky; + + auto& opposet = greedy.size() ? shrinky + : greedy; + + const int padSize = (AvailableAdjacentSpace - usedAdjacentSpace) / theSet.size(); + resetWorkingDimensions(); + + auto padFormula = [&](bindings::Element material) -> int + { return material.get_dimens().get(Adjacent) + padSize; }; + + GeometryProposal childProposal = officialProposal; + childProposal.shrinkForAngle(Adjacent) = false; + + applyToSet(theSet, childProposal, padFormula); + + //One last problem, we gotta add the opposets to the usedAdjacent and Opposite(?) spaces... + //(This could maybe be abstracted with some code from applyToSet TODO) + for(bindings::Element m : opposet) + { + const Dimens d = m.get_dimens(); + if(d.get(Opposite) > usedOppositeSpace) + { + //Does this ever happen????? TODO + usedOppositeSpace = d.get(Opposite); + invalidOpposite = true; + } + usedAdjacentSpace += d.get(Adjacent); + } + + adjustOpposite(); + } + } + + Dimens usedSpace; + usedSpace.get(Adjacent) = usedAdjacentSpace; + usedSpace.get(Opposite) = usedOppositeSpace; + + myDimens = usedSpace; + + //-----------------------------------------Now compute offsets + int adjacentOffset = 0; + const int oppositeOffset = 0; + + applyToChildren([&](bindings::Element child) + { + Coords childCoords; + childCoords.get(Adjacent) = adjacentOffset; + childCoords.get(Opposite) = oppositeOffset; + + child.set_offset(childCoords); + + adjacentOffset += child.get_dimens().get(Adjacent); + }); +} + +}//std_widgets +}//Widgets diff --git a/src/widgets/boxlayout.h b/src/widgets/boxlayout.h new file mode 100644 index 0000000..4259ffb --- /dev/null +++ b/src/widgets/boxlayout.h @@ -0,0 +1,51 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include "user_include/cpp/IVD_cpp.h" + +namespace IVD +{ + +namespace std_widgets +{ + +class BoxLayout : public bindings::UserLayout +{ + const Angle Adjacent; + +public: + BoxLayout(IVD_Environment* theEnv, const Angle theAngel): + bindings::UserLayout(theEnv), + Adjacent(theAngel) {} + ~BoxLayout() override {} + + virtual FillPrecedence get_fill_precedence(const Angle angle) final + { return any_greedy_children_for_angle(angle); } + + virtual void shape(const GeometryProposal officialProposal) final; + + virtual void draw(bindings::Canvas theCanvas) final + { render_children_unordered(); } + + virtual void bubble_children() final + { bubble_children_unordered(); } +}; + +class VboxLayout : public BoxLayout +{ +public: + VboxLayout(IVD_Environment* theEnv): BoxLayout(theEnv, Angle::Vertical) {} + ~VboxLayout() override {} +}; + +class HboxLayout : public BoxLayout +{ +public: + HboxLayout(IVD_Environment* theEnv): BoxLayout(theEnv, Angle::Horizontal) {} + ~HboxLayout() override {} +}; + + +}//std_widgets +}//IVD diff --git a/src/widgets/image.cpp b/src/widgets/image.cpp new file mode 100644 index 0000000..d3cd5b7 --- /dev/null +++ b/src/widgets/image.cpp @@ -0,0 +1,65 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "image.h" + + +namespace IVD +{ +namespace std_widgets +{ + +std::unique_ptr> ImageWidget::imageCache + = {OIIO::ImageCache::create(), [](OIIO::ImageCache* cache) { OIIO::ImageCache::destroy(cache); }}; + +void ImageWidget::shape(const GeometryProposal officialProposal) +{ + OIIO::ImageSpec spec; + const char* imagePath = "oof need custom attributes TODO"; + bool stat = imageCache->get_imagespec(OIIO::ustring(imagePath), spec); + + if(stat) + //This is about as simple as it gets. + myDimens = officialProposal.roundConflicts(Dimens(spec.width, spec.height)); + else + myDimens = officialProposal.proposedDimensions; +} + +void ImageWidget::draw(bindings::Canvas theCanvas) +{ + const OIIO::ustring path("aiufdshaoiuha"); + + OIIO::ImageSpec spec; + bool stat = imageCache->get_imagespec(path, spec); + + if(!stat) return; + + const int channelCount = spec.nchannels > 4 ? 4 + : spec.nchannels; + + std::vector pixels(spec.width * spec.height * channelCount); + + stat = imageCache->get_pixels(path, + 0, 0, + spec.x, spec.x + spec.width, + spec.y, spec.y + spec.height, + spec.z, spec.z + spec.depth, + 0, channelCount, + OIIO::TypeDesc::UCHAR, &pixels[0]); + + if(!stat) + { + std::cerr << imageCache->geterror() << std::endl; + return; + } + + theCanvas.drawBitmapRGBoptionalA(Coords(), + spec.width, + spec.height, + 0, + channelCount, + &pixels[0]); +} + + +}//std_widgets +}//IVD diff --git a/src/widgets/image.h b/src/widgets/image.h new file mode 100644 index 0000000..9744efb --- /dev/null +++ b/src/widgets/image.h @@ -0,0 +1,30 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + + +#include "user_include/cpp/IVD_cpp.h" + +#include "OpenImageIO/imagecache.h" + +namespace IVD +{ +namespace std_widgets +{ + +class ImageWidget : public bindings::UserWidget +{ + //TODO not threadsafe (but does it matter?) + static std::unique_ptr> imageCache; + +public: + virtual FillPrecedence get_fill_precedence(const Angle) + { return FillPrecedence::Shrinky; } + + virtual void shape(const GeometryProposal officialProposal); + virtual void draw(bindings::Canvas theCanvas); +}; + + +}//std_widgets +}//IVD diff --git a/src/widgets/stacklayout.cpp b/src/widgets/stacklayout.cpp new file mode 100644 index 0000000..be2bcb2 --- /dev/null +++ b/src/widgets/stacklayout.cpp @@ -0,0 +1,57 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#include "stacklayout.h" + +namespace IVD +{ +namespace std_widgets +{ + +void StackLayout::shape(const GeometryProposal officalProposal) +{ + std::vector initialSizes; + + applyToChildren([&](bindings::Element elem) + { + elem.shape(officalProposal); + initialSizes.push_back(elem.get_dimens()); + }); + + Dimens maxed; + + auto maximizeForAngle = [&](const Angle theAngle) + { + auto getLargerAnglePredicate = [&](const Angle theAngle) + { + return [=](const Dimens& left, const Dimens& right) + { + return left.get(theAngle) < right.get(theAngle); + }; + }; + + auto it = std::max_element(initialSizes.begin(), + initialSizes.end(), + getLargerAnglePredicate(theAngle)); + + maxed.get(theAngle) = it != initialSizes.end() ? it->get(theAngle) + : officalProposal.proposedDimensions.get(theAngle); + }; + + maximizeForAngle(Angle::Horizontal); + maximizeForAngle(Angle::Vertical); + + //Allow shrink/grow all false by default + GeometryProposal fin; + fin.proposedDimensions = maxed; + + applyToChildren([&](bindings::Element elem) + { + elem.shape(fin); + elem.set_offset(Coords()); + }); + + myDimens = maxed; +} + +}//std_widgets +}//IVD diff --git a/src/widgets/stacklayout.h b/src/widgets/stacklayout.h new file mode 100644 index 0000000..d2234ac --- /dev/null +++ b/src/widgets/stacklayout.h @@ -0,0 +1,47 @@ +//This file is part of the IVD project and is licensed under the terms of the LGPL-3.0-only + +#pragma once + +#include "user_include/cpp/IVD_cpp.h" + +namespace IVD +{ +namespace std_widgets +{ + +class StackLayout : public bindings::UserLayout +{ + //basically reverse insertion order + void applyToChildrenInBubbleOrder(std::function fun) + { + std::vector elements; + + applyToChildren([&](bindings::Element elem) + { elements.push_back(elem); }); + + for(auto rit = elements.rbegin(); rit != elements.rend(); ++rit) + fun(*rit); + } + +public: + StackLayout(IVD_Environment* theEnv): bindings::UserLayout(theEnv) {} + virtual FillPrecedence get_fill_precedence(const Angle angel) + { return any_greedy_children_for_angle(angel); } + + virtual void shape(const GeometryProposal officialProposal); + + virtual void draw(bindings::Canvas theCanvas) + { + applyToChildren([&](bindings::Element elem) + { elem.render(); }); + } + + virtual void bubble_children() + { + applyToChildrenInBubbleOrder([&](bindings::Element elem) + { elem.bubble(); }); + } +}; + +}//std_widgets +}//IVD