diff --git a/godot/LICENSE b/godot/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/godot/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableControl.svg b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableControl.svg new file mode 100644 index 0000000..83df2f9 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableControl.svg @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableControl.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableControl.svg.import new file mode 100644 index 0000000..ff99cd5 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableControl.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b61f2s0sdeivn" +path="res://.godot/imported/AnimatableControl.svg-ea6d8910d2b3058bfac46c0758163c04.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableControl.svg" +dest_files=["res://.godot/imported/AnimatableControl.svg-ea6d8910d2b3058bfac46c0758163c04.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableMount.svg b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableMount.svg new file mode 100644 index 0000000..7da422c --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableMount.svg @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableMount.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableMount.svg.import new file mode 100644 index 0000000..c640fde --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableMount.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://td5qpky5w1jp" +path="res://.godot/imported/AnimatableMount.svg-c55e9d75134e187a953286234a72f1d8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableMount.svg" +dest_files=["res://.godot/imported/AnimatableMount.svg-c55e9d75134e187a953286234a72f1d8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableScrollControl.svg b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableScrollControl.svg new file mode 100644 index 0000000..533bf7e --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableScrollControl.svg @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableScrollControl.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableScrollControl.svg.import new file mode 100644 index 0000000..216bb56 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableScrollControl.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d0wty6o4l0n16" +path="res://.godot/imported/AnimatableScrollControl.svg-82ff252057e45e07beda388858928afc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableScrollControl.svg" +dest_files=["res://.godot/imported/AnimatableScrollControl.svg-82ff252057e45e07beda388858928afc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableTransformationMount.svg b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableTransformationMount.svg new file mode 100644 index 0000000..5cef084 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableTransformationMount.svg @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableTransformationMount.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableTransformationMount.svg.import new file mode 100644 index 0000000..6e9a33f --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableTransformationMount.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c05srll0xjnus" +path="res://.godot/imported/AnimatableTransformationMount.svg-3384cfe8cf7ff650173fe5f295bf32bd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableTransformationMount.svg" +dest_files=["res://.godot/imported/AnimatableTransformationMount.svg-3384cfe8cf7ff650173fe5f295bf32bd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableVisibleControl.svg b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableVisibleControl.svg new file mode 100644 index 0000000..ec72ab6 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableVisibleControl.svg @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableVisibleControl.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableVisibleControl.svg.import new file mode 100644 index 0000000..01017ce --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableVisibleControl.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://3bug5vfuq1vw" +path="res://.godot/imported/AnimatableVisibleControl.svg-231049b2351dc86fdfb539faef83c988.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableVisibleControl.svg" +dest_files=["res://.godot/imported/AnimatableVisibleControl.svg-231049b2351dc86fdfb539faef83c988.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableZoneControl.svg b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableZoneControl.svg new file mode 100644 index 0000000..cb303f8 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableZoneControl.svg @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatableZoneControl.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableZoneControl.svg.import new file mode 100644 index 0000000..9aba2a1 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatableZoneControl.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cice0m617g5ks" +path="res://.godot/imported/AnimatableZoneControl.svg-52fb2c5380b72522d734b8b9ba5fc752.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatableZoneControl.svg" +dest_files=["res://.godot/imported/AnimatableZoneControl.svg-52fb2c5380b72522d734b8b9ba5fc752.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatedSwitch.svg b/godot/addons/FreeControl/assets/icons/CustomType/AnimatedSwitch.svg new file mode 100644 index 0000000..c6e0000 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatedSwitch.svg @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/AnimatedSwitch.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/AnimatedSwitch.svg.import new file mode 100644 index 0000000..2825389 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/AnimatedSwitch.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b40v8yi30eall" +path="res://.godot/imported/AnimatedSwitch.svg-76f991124fb01f03d12bfe5e3a3c31ba.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/AnimatedSwitch.svg" +dest_files=["res://.godot/imported/AnimatedSwitch.svg-76f991124fb01f03d12bfe5e3a3c31ba.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/BoundsCheck.svg b/godot/addons/FreeControl/assets/icons/CustomType/BoundsCheck.svg new file mode 100644 index 0000000..ca2498b --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/BoundsCheck.svg @@ -0,0 +1,50 @@ + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/BoundsCheck.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/BoundsCheck.svg.import new file mode 100644 index 0000000..939597c --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/BoundsCheck.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bladvsiu7rha1" +path="res://.godot/imported/BoundsCheck.svg-02b170ef6b912468a1f094b439993793.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/BoundsCheck.svg" +dest_files=["res://.godot/imported/BoundsCheck.svg-02b170ef6b912468a1f094b439993793.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/Carousel.svg b/godot/addons/FreeControl/assets/icons/CustomType/Carousel.svg new file mode 100644 index 0000000..1bc75d3 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/Carousel.svg @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/Carousel.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/Carousel.svg.import new file mode 100644 index 0000000..eafbc98 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/Carousel.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://vu8og3rtsflq" +path="res://.godot/imported/Carousel.svg-24a12fa029ec5088ae1cb278064dea40.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/Carousel.svg" +dest_files=["res://.godot/imported/Carousel.svg-24a12fa029ec5088ae1cb278064dea40.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/CircularContainer.svg b/godot/addons/FreeControl/assets/icons/CustomType/CircularContainer.svg new file mode 100644 index 0000000..b65620d --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/CircularContainer.svg @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/CircularContainer.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/CircularContainer.svg.import new file mode 100644 index 0000000..691b1f3 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/CircularContainer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c00ttqalyh2ck" +path="res://.godot/imported/CircularContainer.svg-1dd941875614a9abaf152e4d2a3a686b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/CircularContainer.svg" +dest_files=["res://.godot/imported/CircularContainer.svg-1dd941875614a9abaf152e4d2a3a686b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/DistanceCheck.svg b/godot/addons/FreeControl/assets/icons/CustomType/DistanceCheck.svg new file mode 100644 index 0000000..44d1953 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/DistanceCheck.svg @@ -0,0 +1,50 @@ + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/DistanceCheck.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/DistanceCheck.svg.import new file mode 100644 index 0000000..4eb267f --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/DistanceCheck.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://den188fn44ahq" +path="res://.godot/imported/DistanceCheck.svg-cc9439b9bfdf47f875ceab8615f27948.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/DistanceCheck.svg" +dest_files=["res://.godot/imported/DistanceCheck.svg-cc9439b9bfdf47f875ceab8615f27948.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/Drawer.svg b/godot/addons/FreeControl/assets/icons/CustomType/Drawer.svg new file mode 100644 index 0000000..8cc649a --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/Drawer.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/Drawer.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/Drawer.svg.import new file mode 100644 index 0000000..60cee16 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/Drawer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://lje075l441hl" +path="res://.godot/imported/Drawer.svg-7020a44fe2fbf3bc7e577f6647e28243.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/Drawer.svg" +dest_files=["res://.godot/imported/Drawer.svg-7020a44fe2fbf3bc7e577f6647e28243.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/HoldButton.svg b/godot/addons/FreeControl/assets/icons/CustomType/HoldButton.svg new file mode 100644 index 0000000..f069eb5 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/HoldButton.svg @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/HoldButton.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/HoldButton.svg.import new file mode 100644 index 0000000..5fa6ea9 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/HoldButton.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ceqkp35yu0v27" +path="res://.godot/imported/HoldButton.svg-059d8d0ebedf00f7d7ee555447e9ac85.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/HoldButton.svg" +dest_files=["res://.godot/imported/HoldButton.svg-059d8d0ebedf00f7d7ee555447e9ac85.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/MaxRatioContainer.svg b/godot/addons/FreeControl/assets/icons/CustomType/MaxRatioContainer.svg new file mode 100644 index 0000000..e97ad44 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/MaxRatioContainer.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/MaxRatioContainer.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/MaxRatioContainer.svg.import new file mode 100644 index 0000000..a6a212a --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/MaxRatioContainer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://crwd74vrtbrmm" +path="res://.godot/imported/MaxRatioContainer.svg-69252897895331776d794bb3e7d4450a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/MaxRatioContainer.svg" +dest_files=["res://.godot/imported/MaxRatioContainer.svg-69252897895331776d794bb3e7d4450a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/MaxSizeContainer.svg b/godot/addons/FreeControl/assets/icons/CustomType/MaxSizeContainer.svg new file mode 100644 index 0000000..964b200 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/MaxSizeContainer.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/MaxSizeContainer.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/MaxSizeContainer.svg.import new file mode 100644 index 0000000..14cec2f --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/MaxSizeContainer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cj5vpwdi8b2t4" +path="res://.godot/imported/MaxSizeContainer.svg-c449fe736ac19e4da4954b352d7a4bc0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/MaxSizeContainer.svg" +dest_files=["res://.godot/imported/MaxSizeContainer.svg-c449fe736ac19e4da4954b352d7a4bc0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionButton.svg b/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionButton.svg new file mode 100644 index 0000000..578dcd1 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionButton.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionButton.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionButton.svg.import new file mode 100644 index 0000000..6e62576 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionButton.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bf8ifq48pj2iv" +path="res://.godot/imported/ModulateTransitionButton.svg-124cc46ae542378e7b346986eb52d0c7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/ModulateTransitionButton.svg" +dest_files=["res://.godot/imported/ModulateTransitionButton.svg-124cc46ae542378e7b346986eb52d0c7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionContainer.svg b/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionContainer.svg new file mode 100644 index 0000000..65bb359 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionContainer.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionContainer.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionContainer.svg.import new file mode 100644 index 0000000..5eaed84 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/ModulateTransitionContainer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://s3hp67f43n0x" +path="res://.godot/imported/ModulateTransitionContainer.svg-41f98f4655621d9ed04421a4b13baee4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/ModulateTransitionContainer.svg" +dest_files=["res://.godot/imported/ModulateTransitionContainer.svg-41f98f4655621d9ed04421a4b13baee4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/MotionCheck.svg b/godot/addons/FreeControl/assets/icons/CustomType/MotionCheck.svg new file mode 100644 index 0000000..03fe805 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/MotionCheck.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/MotionCheck.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/MotionCheck.svg.import new file mode 100644 index 0000000..498b4b8 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/MotionCheck.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jywj8x8u5pop" +path="res://.godot/imported/MotionCheck.svg-5facd6c8a5eea77e7e6606c52a8d456d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/MotionCheck.svg" +dest_files=["res://.godot/imported/MotionCheck.svg-5facd6c8a5eea77e7e6606c52a8d456d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/PaddingContainer.svg b/godot/addons/FreeControl/assets/icons/CustomType/PaddingContainer.svg new file mode 100644 index 0000000..f1edea1 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/PaddingContainer.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/PaddingContainer.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/PaddingContainer.svg.import new file mode 100644 index 0000000..3a078c4 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/PaddingContainer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6o6frklk2afj" +path="res://.godot/imported/PaddingContainer.svg-9d29b77cc431f8e4258d6655803790fe.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/PaddingContainer.svg" +dest_files=["res://.godot/imported/PaddingContainer.svg-9d29b77cc431f8e4258d6655803790fe.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/Page.svg b/godot/addons/FreeControl/assets/icons/CustomType/Page.svg new file mode 100644 index 0000000..fd3cb6c --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/Page.svg @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/Page.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/Page.svg.import new file mode 100644 index 0000000..d3301b3 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/Page.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqtdlpg3h0j5d" +path="res://.godot/imported/Page.svg-d8188a8e86ac732bd815acd1e14bc1cd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/Page.svg" +dest_files=["res://.godot/imported/Page.svg-d8188a8e86ac732bd815acd1e14bc1cd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/PageInfo.svg b/godot/addons/FreeControl/assets/icons/CustomType/PageInfo.svg new file mode 100644 index 0000000..61c8cc8 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/PageInfo.svg @@ -0,0 +1 @@ + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/PageInfo.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/PageInfo.svg.import new file mode 100644 index 0000000..8315bbb --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/PageInfo.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2wlsev3wn5rq" +path="res://.godot/imported/PageInfo.svg-cac5d98635bfc001a76d9044e0d773df.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/PageInfo.svg" +dest_files=["res://.godot/imported/PageInfo.svg-cac5d98635bfc001a76d9044e0d773df.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/ProportionalContainer.svg b/godot/addons/FreeControl/assets/icons/CustomType/ProportionalContainer.svg new file mode 100644 index 0000000..19afd07 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/ProportionalContainer.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/ProportionalContainer.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/ProportionalContainer.svg.import new file mode 100644 index 0000000..5d6f67a --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/ProportionalContainer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d08yq8cd27f3f" +path="res://.godot/imported/ProportionalContainer.svg-71fab0e839b225c75a1402a08ebf61b4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/ProportionalContainer.svg" +dest_files=["res://.godot/imported/ProportionalContainer.svg-71fab0e839b225c75a1402a08ebf61b4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/RouterStack.svg b/godot/addons/FreeControl/assets/icons/CustomType/RouterStack.svg new file mode 100644 index 0000000..fcebc78 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/RouterStack.svg @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/RouterStack.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/RouterStack.svg.import new file mode 100644 index 0000000..a6c4585 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/RouterStack.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bx72t0x80xoc" +path="res://.godot/imported/RouterStack.svg-943b2eec7eb910602fd6b012fee2113d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/RouterStack.svg" +dest_files=["res://.godot/imported/RouterStack.svg-943b2eec7eb910602fd6b012fee2113d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionButton.svg b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionButton.svg new file mode 100644 index 0000000..bd3be2d --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionButton.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionButton.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionButton.svg.import new file mode 100644 index 0000000..99b1c0b --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionButton.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://beombqir8gvsg" +path="res://.godot/imported/StyleTransitionButton.svg-31239487ac3b145d1d200758ae441fa7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/StyleTransitionButton.svg" +dest_files=["res://.godot/imported/StyleTransitionButton.svg-31239487ac3b145d1d200758ae441fa7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionContainer.svg b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionContainer.svg new file mode 100644 index 0000000..9426722 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionContainer.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionContainer.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionContainer.svg.import new file mode 100644 index 0000000..4351a9b --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionContainer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c6dj30vpi468i" +path="res://.godot/imported/StyleTransitionContainer.svg-d423c826296d72d4b9686de202d9c396.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/StyleTransitionContainer.svg" +dest_files=["res://.godot/imported/StyleTransitionContainer.svg-d423c826296d72d4b9686de202d9c396.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionPanel.svg b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionPanel.svg new file mode 100644 index 0000000..83d7088 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionPanel.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionPanel.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionPanel.svg.import new file mode 100644 index 0000000..6814903 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/StyleTransitionPanel.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cbu1p6if3bhsq" +path="res://.godot/imported/StyleTransitionPanel.svg-6e7803726b3f0f97dacbb20fcb7a611c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/StyleTransitionPanel.svg" +dest_files=["res://.godot/imported/StyleTransitionPanel.svg-6e7803726b3f0f97dacbb20fcb7a611c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/assets/icons/CustomType/SwapContainer.svg b/godot/addons/FreeControl/assets/icons/CustomType/SwapContainer.svg new file mode 100644 index 0000000..adc6a25 --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/SwapContainer.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + diff --git a/godot/addons/FreeControl/assets/icons/CustomType/SwapContainer.svg.import b/godot/addons/FreeControl/assets/icons/CustomType/SwapContainer.svg.import new file mode 100644 index 0000000..9db3b9f --- /dev/null +++ b/godot/addons/FreeControl/assets/icons/CustomType/SwapContainer.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b6ebfwbec45wl" +path="res://.godot/imported/SwapContainer.svg-2f0195c9a4f5afd5ec3524f1349104ed.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/FreeControl/assets/icons/CustomType/SwapContainer.svg" +dest_files=["res://.godot/imported/SwapContainer.svg-2f0195c9a4f5afd5ec3524f1349104ed.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/godot/addons/FreeControl/main.gd b/godot/addons/FreeControl/main.gd new file mode 100644 index 0000000..ececaf3 --- /dev/null +++ b/godot/addons/FreeControl/main.gd @@ -0,0 +1,258 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +extends EditorPlugin + +const GLOBAL_FOLDER := "res://addons/FreeControl/src/Other/Global/" +const CUSTOM_CLASS_FOLDER := "res://addons/FreeControl/src/CustomClasses/" +const ICON_FOLDER := "res://addons/FreeControl/assets/icons/CustomType/" + +func _enter_tree() -> void: + # AnimatableControls + # Control + add_custom_type( + "AnimatableControl", + "Container", + load(CUSTOM_CLASS_FOLDER + "AnimatableControl/control/AnimatableControl.gd"), + load(ICON_FOLDER + "AnimatableControl.svg") + ) + add_custom_type( + "AnimatableScrollControl", + "Container", + load(CUSTOM_CLASS_FOLDER + "AnimatableControl/control/AnimatableScrollControl.gd"), + load(ICON_FOLDER + "AnimatableScrollControl.svg") + ) + add_custom_type( + "AnimatableZoneControl", + "Container", + load(CUSTOM_CLASS_FOLDER + "AnimatableControl/control/AnimatableZoneControl.gd"), + load(ICON_FOLDER + "AnimatableZoneControl.svg") + ) + add_custom_type( + "AnimatableVisibleControl", + "Container", + load(CUSTOM_CLASS_FOLDER + "AnimatableControl/control/AnimatableVisibleControl.gd"), + load(ICON_FOLDER + "AnimatableVisibleControl.svg") + ) + # Mount + add_custom_type( + "AnimatableMount", + "Control", + load(CUSTOM_CLASS_FOLDER + "AnimatableControl/mount/AnimatableMount.gd"), + load(ICON_FOLDER + "AnimatableMount.svg") + ) + add_custom_type( + "AnimatableTransformationMount", + "Control", + load(CUSTOM_CLASS_FOLDER + "AnimatableControl/mount/AnimatableTransformationMount.gd"), + load(ICON_FOLDER + "AnimatableTransformationMount.svg") + ) + + # Buttons + # Base + add_custom_type( + "AnimatedSwitch", + "BaseButton", + load(CUSTOM_CLASS_FOLDER + "Buttons/Base/AnimatedSwitch.gd"), + load(ICON_FOLDER + "AnimatedSwitch.svg") + ) + add_custom_type( + "HoldButton", + "BaseButton", + load(CUSTOM_CLASS_FOLDER + "Buttons/Base/HoldButton.gd"), + load(ICON_FOLDER + "HoldButton.svg") + ) + # MotionCheck + add_custom_type( + "BoundsCheck", + "Control", + load(CUSTOM_CLASS_FOLDER + "Buttons/Base/MotionCheck/BoundsCheck.gd"), + load(ICON_FOLDER + "BoundsCheck.svg") + ) + add_custom_type( + "DistanceCheck", + "Control", + load(CUSTOM_CLASS_FOLDER + "Buttons/Base/MotionCheck/DistanceCheck.gd"), + load(ICON_FOLDER + "DistanceCheck.svg") + ) + add_custom_type( + "MotionCheck", + "Control", + load(CUSTOM_CLASS_FOLDER + "Buttons/Base/MotionCheck/MotionCheck.gd"), + load(ICON_FOLDER + "MotionCheck.svg") + ) + + # Complex + add_custom_type( + "ModulateTransitionButton", + "Container", + load(CUSTOM_CLASS_FOLDER + "Buttons/Complex/ModulateTransitionButton.gd"), + load(ICON_FOLDER + "ModulateTransitionButton.svg") + ) + add_custom_type( + "StyleTransitionButton", + "Container", + load(CUSTOM_CLASS_FOLDER + "Buttons/Complex/StyleTransitionButton.gd"), + load(ICON_FOLDER + "StyleTransitionButton.svg") + ) + + # Carousel + add_custom_type( + "Carousel", + "Container", + load(CUSTOM_CLASS_FOLDER + "Carousel/Carousel.gd"), + load(ICON_FOLDER + "Carousel.svg") + ) + + # CircularContainer + add_custom_type( + "CircularContainer", + "Container", + load(CUSTOM_CLASS_FOLDER + "CircularContainer/CircularContainer.gd"), + load(ICON_FOLDER + "CircularContainer.svg") + ) + + # Drawer + add_custom_type( + "Drawer", + "Container", + load(CUSTOM_CLASS_FOLDER + "Drawer/Drawer.gd"), + load(ICON_FOLDER + "Drawer.svg") + ) + + # PaddingContainer + add_custom_type( + "PaddingContainer", + "Container", + load(CUSTOM_CLASS_FOLDER + "PaddingContainer/PaddingContainer.gd"), + load(ICON_FOLDER + "PaddingContainer.svg") + ) + + # ProportionalContainer + add_custom_type( + "ProportionalContainer", + "Container", + load(CUSTOM_CLASS_FOLDER + "ProportionalContainer/ProportionalContainer.gd"), + load(ICON_FOLDER + "ProportionalContainer.svg") + ) + + # Routers + add_custom_type( + "RouterStack", + "Container", + load(CUSTOM_CLASS_FOLDER + "Routers/RouterStack.gd"), + load(ICON_FOLDER + "RouterStack.svg") + ) + # Base + add_custom_type( + "Page", + "Container", + load(CUSTOM_CLASS_FOLDER + "Routers/Base/Page.gd"), + load(ICON_FOLDER + "Page.svg") + ) + add_custom_type( + "PageInfo", + "Resource", + load(CUSTOM_CLASS_FOLDER + "Routers/Base/PageInfo.gd"), + load(ICON_FOLDER + "PageInfo.svg") + ) + + # SizeControllers + # MaxSizeContainer + add_custom_type( + "MaxSizeContainer", + "Container", + load(CUSTOM_CLASS_FOLDER + "SizeController/MaxSizeContainer.gd"), + load(ICON_FOLDER + "MaxSizeContainer.svg") + ) + # MaxRatioContainer + add_custom_type( + "MaxRatioContainer", + "Container", + load(CUSTOM_CLASS_FOLDER + "SizeController/MaxRatioContainer.gd"), + load(ICON_FOLDER + "MaxRatioContainer.svg") + ) + + # SwapContainer + add_custom_type( + "SwapContainer", + "Container", + load(CUSTOM_CLASS_FOLDER + "SwapContainer/SwapContainer.gd"), + load(ICON_FOLDER + "SwapContainer.svg") + ) + + # TransitionContainers + add_custom_type( + "ModulateTransitionContainer", + "Container", + load(CUSTOM_CLASS_FOLDER + "TransitionContainers/ModulateTransitionContainer.gd"), + load(ICON_FOLDER + "ModulateTransitionContainer.svg") + ) + add_custom_type( + "StyleTransitionContainer", + "Container", + load(CUSTOM_CLASS_FOLDER + "TransitionContainers/StyleTransitionContainer.gd"), + load(ICON_FOLDER + "StyleTransitionContainer.svg") + ) + add_custom_type( + "StyleTransitionPanel", + "Container", + load(CUSTOM_CLASS_FOLDER + "TransitionContainers/StyleTransitionPanel.gd"), + load(ICON_FOLDER + "StyleTransitionPanel.svg") + ) + +func _exit_tree() -> void: + # AnimatableControls + # Control + remove_custom_type("AnimatableControl") + remove_custom_type("AnimatableScrollControl") + remove_custom_type("AnimatableZoneControl") + remove_custom_type("AnimatableVisibleControl") + # Mount + remove_custom_type("AnimatableMount") + remove_custom_type("AnimatableTransformationMount") + + # Buttons + # Base + remove_custom_type("AnimatedSwitch") + remove_custom_type("HoldButton") + # MotionCheck + remove_custom_type("BoundsCheck") + remove_custom_type("DistanceCheck") + remove_custom_type("MotionCheck") + + # Complex + remove_custom_type("ModulateTransitionButton") + remove_custom_type("StyleTransitionButton") + + # Carousel + remove_custom_type("Carousel") + + # CircularContainer + remove_custom_type("CircularContainer") + + # Drawer + remove_custom_type("Drawer") + + # PaddingContainer + remove_custom_type("PaddingContainer") + + # ProportionalContainer + remove_custom_type("ProportionalContainer") + + # Routers + remove_custom_type("RouterStack") + # Base + remove_custom_type("Page") + remove_custom_type("PageInfo") + + # SizeControllers + remove_custom_type("MaxSizeContainer") + remove_custom_type("MaxRatioContainer") + + # SwapContainer + remove_custom_type("SwapContainer") + + # TransitionContainers + remove_custom_type("ModulateTransitionContainer") + remove_custom_type("StyleTransitionContainer") + remove_custom_type("StyleTransitionPanel") diff --git a/godot/addons/FreeControl/plugin.cfg b/godot/addons/FreeControl/plugin.cfg new file mode 100644 index 0000000..836b476 --- /dev/null +++ b/godot/addons/FreeControl/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="FreeControl" +description="Multiple Control nodes for easier UI manipulation" +author="Xavier Alvarez" +version="1.5.1" +script="main.gd" diff --git a/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableControl.gd b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableControl.gd new file mode 100644 index 0000000..b487b5f --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableControl.gd @@ -0,0 +1,139 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name AnimatableControl extends Container +## A container to be used for free transformation within a UI. + +## This signal emits when one of the following properties change: scale, position, +## rotation, pivot_offset +signal transformation_changed + +## The size mode this node's size will be bounded by. +enum SIZE_MODE { + NONE = 0b00, ## This node's size is not bounded + MIN = 0b01, ## This node's size will be greater than or equal to this node's mount size + MAX = 0b10, ## This node's size will be less than or equal to this node's mount size + EXACT = 0b11 ## This node's size will be the same as this node's mount size +} + +## Controls how this node's size is bounded, according to the node's mount size +@export var size_mode : SIZE_MODE = SIZE_MODE.EXACT: + set(val): + if size_mode != val: + size_mode = val + if _mount: + _mount.update_minimum_size() + _bound_size() + notify_property_list_changed() +## Auto sets the pivot to be at some position percentage of the size. +@export var pivot_ratio : Vector2: + set(val): + if pivot_ratio != val: + pivot_ratio = val + pivot_offset = size * val + +var _mount : AnimatableMount + +func _get_configuration_warnings() -> PackedStringArray: + if get_parent() is AnimatableMount: + return [] + return ["This node only serves to be animatable within a UI. Please only attach as a child to a 'AnimatableMount' node."] +func _validate_property(property: Dictionary) -> void: + if property.name in ["layout_mode", "anchors_preset"]: + property.usage |= PROPERTY_USAGE_READ_ONLY + elif property.name == "size": + if size_mode == SIZE_MODE.EXACT: + property.usage |= PROPERTY_USAGE_READ_ONLY +func _set(property: StringName, value: Variant) -> bool: + if property in ["scale", "position", "rotation"]: + transformation_changed.emit() + _bound_size() + elif property == "pivot_offset": + if is_node_ready() && size > Vector2.ZERO && pivot_offset != value: + pivot_ratio = pivot_offset / size + transformation_changed.emit() + return false + +func _init() -> void: + resized.connect(_handle_resize) + sort_children.connect(_sort_children) + tree_exited.connect(_on_tree_exit) + tree_entered.connect(_on_tree_enter) + item_rect_changed.connect(transformation_changed.emit) +func _on_tree_enter() -> void: + _mount = (get_parent() as AnimatableMount) + if _mount: + _mount._on_mount(self) +func _on_tree_exit() -> void: + if _mount: + _mount._on_unmount(self) + _mount = null + + +func _handle_resize() -> void: + _bound_size() + _update_pivot() +func _update_pivot() -> void: + set_pivot_offset(pivot_ratio * size) + transformation_changed.emit() +func _sort_children() -> void: + for child : Control in _get_control_children(): + _resize_child(child) +func _resize_child(child : Control) -> void: + var child_size := child.get_combined_minimum_size() + var set_pos : Vector2 + + match child.size_flags_horizontal & ~SIZE_EXPAND: + SIZE_FILL: + set_pos.x = 0 + child_size.x = max(child_size.x, size.x) + SIZE_SHRINK_BEGIN: + set_pos.x = 0 + SIZE_SHRINK_CENTER: + set_pos.x = (size.x - child_size.x) * 0.5 + SIZE_SHRINK_END: + set_pos.x = size.x - child_size.x + match child.size_flags_vertical & ~SIZE_EXPAND: + SIZE_FILL: + set_pos.y = 0 + child_size.y = max(child_size.y, size.y) + SIZE_SHRINK_BEGIN: + set_pos.y = 0 + SIZE_SHRINK_CENTER: + set_pos.y = (size.y - child_size.y) * 0.5 + SIZE_SHRINK_END: + set_pos.y = size.y - child_size.y + + fit_child_in_rect(child, Rect2(set_pos, child_size)) +func _get_minimum_size() -> Vector2: + if clip_children: return Vector2.ZERO + + var min_size := Vector2.ZERO + for child : Control in _get_control_children(): + min_size = min_size.max(child.get_combined_minimum_size()) + return min_size + +func _bound_size() -> void: + if !_mount: return + + var new_size : Vector2 = size + if size_mode == SIZE_MODE.MAX: + new_size = _mount.get_relative_size(self).min(size) + elif size_mode == SIZE_MODE.MIN: + new_size = _mount.get_relative_size(self).max(size) + elif size_mode == SIZE_MODE.EXACT: + new_size = _mount.get_relative_size(self) + + if new_size != size: + size = new_size + +func _get_control_children() -> Array[Control]: + var ret : Array[Control] + ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible)) + return ret + +## Gets the mount this node is currently a child to.[br] +## If this node is not a child to any [AnimatableMount] nodes, this returns [code]null[/code] instead. +func get_mount() -> AnimatableMount: + return _mount + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableScrollControl.gd b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableScrollControl.gd new file mode 100644 index 0000000..b63d7cb --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableScrollControl.gd @@ -0,0 +1,72 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name AnimatableScrollControl extends AnimatableControl +## A container to be used for free transformation, within a UI, depended on a [ScrollContainer]'s scroll progress. + +## The [ScrollContainer] this node will consider for operations. Is automatically +## set to the closet parent [ScrollContainer] in the tree if [member scroll] is +## [code]null[/code] and [Engine] is in editor mode. +## [br][br] +## [b]NOTE[/b]: It is recomended that this node's [AnimatableMount] is a child of +## [member scroll]. +@export var scroll : ScrollContainer: + set(val): + if scroll != val: + if scroll: + scroll.get_h_scroll_bar().value_changed.disconnect(_scrolled_horizontal) + scroll.get_v_scroll_bar().value_changed.disconnect(_scrolled_vertical) + scroll = val + if val: + val.get_h_scroll_bar().value_changed.connect(_scrolled_horizontal) + val.get_v_scroll_bar().value_changed.connect(_scrolled_vertical) + + if is_node_ready(): + _scrolled_horizontal(val.get_h_scroll_bar().value) + _scrolled_vertical(val.get_v_scroll_bar().value) + +func _enter_tree() -> void: + if !scroll && Engine.is_editor_hint(): scroll = get_parent_scroll() + +## A virtual function that is called when [member scroll] is horizontally scrolled. +## [br][br] +## Paramter [param scroll] is the current horizontal progress of the scroll. +func _scrolled_horizontal(scroll_hor : float) -> void: pass +## A virtual function that is called when [member scroll] is vertically scrolled. +## [br][br] +## Paramter [param scroll] is the current vertical progress of the scroll. +func _scrolled_vertical(scroll_ver : float) -> void: pass + +## Returns the global difference between this node's [AnimatableMount] and +## [member scroll] positions. +func get_origin_offset() -> Vector2: + if !_mount || !scroll: return Vector2.ZERO + return _mount.global_position - scroll.global_position +## Returns the horizontal and vertical progress of [member scroll]. +func get_scroll_offset() -> Vector2: + if !scroll: return Vector2.ZERO + return Vector2(scroll.scroll_horizontal, scroll.scroll_vertical) +## Gets the closet parent [ScrollContainer] in the tree. +func get_parent_scroll() -> ScrollContainer: + var ret : Control = (get_parent() as Control) + while ret != null: + if ret is ScrollContainer: return ret + ret = (ret.get_parent() as Control) + return null + +## Returns a percentage of how visible this node's [AnimatableMount] is, within +## the rect of [member scroll]. +func is_visible_percent() -> float: + if !_mount || !scroll: return 0 + return (_mount.get_global_rect().intersection(scroll.get_global_rect()).get_area()) / (_mount.size.x * _mount.size.y) +## Returns a percentage of how visible this node's [AnimatableMount] is, within the +## horizontal bounds of [member scroll]. +func get_visible_horizontal_percent() -> float: + if !_mount || !scroll: return 0 + return (min(_mount.global_position.x + _mount.size.x, scroll.global_position.x + scroll.size.x) - max(_mount.global_position.x, scroll.global_position.x)) / _mount.size.x +## Returns a percentage of how visible this node's [AnimatableMount] is, within the +## vertical bounds of [member scroll]. +func get_visible_vertical_percent() -> float: + if !_mount || !scroll: return 0 + return (min(_mount.global_position.y + _mount.size.y, scroll.global_position.y + scroll.size.y) - max(_mount.global_position.y, scroll.global_position.y)) / _mount.size.y + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableVisibleControl.gd b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableVisibleControl.gd new file mode 100644 index 0000000..4cc258a --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableVisibleControl.gd @@ -0,0 +1,397 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name AnimatableVisibleControl extends AnimatableScrollControl +## A container to be used for free transformation, within a UI, depending on if +## the node is visible in a [ScrollContainer] scroll. + +## Emitted when requested threshold has been entered. +signal entered_threshold +## Emitted when requested threshold has been exited. +signal exited_threshold +## Emitted when this node's [AnimatableMount]'s rect entered visible range. +signal entered_screen +## Emitted when this node's [AnimatableMount]'s rect exited visible range. +signal exited_screen + +## Modes of threshold type checking. +enum CHECK_MODE { + NONE = 0b000, ## No behavior. + HORIZONTAL = 0b001, ## Only checks horizontally using [member threshold_horizontal]. + VERTICAL = 0b010, ## Only checks vertically using [member threshold_vertical]. + BOTH = 0b011 ## Checks horizontally and vertically. +} + +## Modes of threshold size. +enum THRESHOLD_EDITOR_DIMS { + None = 0b00, ## Both horizontal and vertical axis are based on ratio. + Horizontal = 0b01, ## Horizontal axis is based on exact pixel and vertical on ratio. + Vertical = 0b10, ## Horizontal axis is based on ratio and vertical on exact pixel. + Both = 0b11, ## Both horizontal and vertical axis are based on exact pixel. +} + +## Color for inner highlighting - Indicates when visiblity is required to met +## threshold. +const HIGHLIGHT_COLOR := Color(Color.RED, 0.3) +## Color for overlap highlighting - Indicates when visiblity is required, starting +## from the far end, to met threshold. +const ANTI_HIGHLIGHT_COLOR := Color(Color.DARK_CYAN, 1) +## Color for helpful lines to make highlighting for clear. +const INTERSECT_HIGHLIGHT_COLOR := Color(Color.RED, 0.8) + +@export_group("Mode") +## Sets the mode of threshold type checking. +@export var check_mode: CHECK_MODE = CHECK_MODE.NONE: + set(val): + if check_mode != val: + check_mode = val + notify_property_list_changed() + queue_redraw() + +## A flag variable used to distinguish if the threshold amount is described by +## a ratio of the size of the [member AnimatableScrollControl.scroll] value, or +## by a const pixel value.[br] +## Horizontal and vertical axis are consistered differently. +## [br][br] +## See [enum THRESHOLD_EDITOR_DIMS], [member threshold_horizontal], and [member threshold_vertical]. +var threshold_pixel : int: + set(val): + if (threshold_pixel ^ val) & THRESHOLD_EDITOR_DIMS.Horizontal: + _scrolled_horizontal(get_scroll_offset().x) + if (threshold_pixel ^ val) & THRESHOLD_EDITOR_DIMS.Vertical: + _scrolled_vertical(get_scroll_offset().y) + threshold_pixel = val + notify_property_list_changed() + queue_redraw() +## The minimum horizontal percentage this node's [AnimatableMount]'s rect must be +## visible in [member scroll] for this node to be consistered visible. +var threshold_horizontal : float = 0.5: + set(val): + if threshold_horizontal != val: + threshold_horizontal = val + _scrolled_horizontal(0) + queue_redraw() +## The minimum vertical percentage this node's [AnimatableMount]'s rect must be +## visible in [member scroll] for this node to be consistered visible. +var threshold_vertical : float = 0.5: + set(val): + if threshold_vertical != val: + threshold_vertical = val + _scrolled_vertical(0) + queue_redraw() +## [b]Editor usage only.[/b] Shows or hides the helpful threshold highlighter. +var hide_indicator : bool = true: + set(val): + if hide_indicator != val: + hide_indicator = val + queue_redraw() + +var _last_threshold_horizontal : float +var _last_threshold_vertical : float +var _last_visible : bool + +func _get_property_list() -> Array[Dictionary]: + var ret : Array[Dictionary] = [] + var horizontal : int = 0 if check_mode & CHECK_MODE.HORIZONTAL else PROPERTY_USAGE_READ_ONLY + var vertical : int = 0 if check_mode & CHECK_MODE.VERTICAL else PROPERTY_USAGE_READ_ONLY + var either : int = horizontal & vertical + + var options : String + if !horizontal: options = "Horizontal:1," + if !vertical: options += "Vertical:2" + + ret.append({ + "name": "Threshold", + "type": TYPE_NIL, + "usage": PROPERTY_USAGE_GROUP, + "hint_string": "" + }) + ret.append({ + "name": "threshold_pixel", + "type": TYPE_INT, + "hint": PROPERTY_HINT_FLAGS, + "hint_string": options, + "usage": PROPERTY_USAGE_DEFAULT | either + }) + ret.append({ + "name": "threshold_horizontal", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT | horizontal + }.merged({} if threshold_pixel & 1 else { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,1,0.001" + })) + ret.append({ + "name": "threshold_vertical", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT | vertical + }.merged({} if threshold_pixel & 2 else { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,1,0.001" + })) + + ret.append({ + "name": "Indicator", + "type": TYPE_NIL, + "usage": PROPERTY_USAGE_GROUP, + "hint_str": "" + }) + ret.append({ + "name": "hide_indicator", + "type": TYPE_BOOL, + "usage": PROPERTY_USAGE_DEFAULT + }) + + return ret +func _property_can_revert(property: StringName) -> bool: + if property == "threshold_pixel": + if self[property] != 0: return true + elif property in ["threshold_horizontal", "threshold_vertical"]: + if self[property] != 0.5: return true + elif property == "hide_indicator": + return !hide_indicator + return false +func _property_get_revert(property: StringName) -> Variant: + if property == "threshold_pixel": + return 0 + elif property in ["threshold_horizontal", "threshold_vertical"]: + return 0.5 + elif property == "hide_indicator": + return true + return null + +func _init() -> void: + item_rect_changed.connect(queue_redraw) + +func _get_threshold_size() -> Array[Vector2]: + var ratio_thr : Vector2 + var full_thr : Vector2 + + if is_zero_approx(_mount.size.x): + ratio_thr.x = 1 + full_thr.x = _mount.size.x + elif threshold_pixel & THRESHOLD_EDITOR_DIMS.Horizontal: + var hor := clamp(threshold_horizontal, 0, _mount.size.x) + ratio_thr.x = hor / _mount.size.x + full_thr.x = hor + else: + var hor := clamp(threshold_horizontal, 0, 1) + ratio_thr.x = hor + full_thr.x = hor * _mount.size.x + + if is_zero_approx(_mount.size.y): + ratio_thr.y = 1 + full_thr.y = _mount.size.y + elif threshold_pixel & THRESHOLD_EDITOR_DIMS.Vertical: + var vec := clamp(threshold_vertical, 0, _mount.size.y) + ratio_thr.y = vec / _mount.size.y + full_thr.y = vec + else: + var vec := clamp(threshold_vertical, 0, 1) + ratio_thr.y = vec + full_thr.y = vec * _mount.size.y + + return [ratio_thr, full_thr] +func _draw() -> void: + if !_mount || !Engine.is_editor_hint() || hide_indicator: return + + var threshold_adjust := _get_threshold_size() + + draw_set_transform(-position) + draw_rect(Rect2(Vector2.ZERO, size), Color.CORAL, false) + + match check_mode: + CHECK_MODE.HORIZONTAL: + var left := threshold_adjust[1].x + var right := size.x - left + + if threshold_adjust[0].x > 0.5: + left = size.x - left + right = size.x - right + + _draw_highlight( + left, + 0, + right, + size.y, + threshold_adjust[0].x < 0.5 + ) + CHECK_MODE.VERTICAL: + var top := threshold_adjust[1].y + var bottom := size.y - top + + if threshold_adjust[0].y > 0.5: + top = size.y - top + bottom = size.y - bottom + + _draw_highlight( + 0, + top, + size.x, + bottom, + threshold_adjust[0].y < 0.5 + ) + CHECK_MODE.BOTH: + var left := threshold_adjust[1].x + var right := size.x - left + var top := threshold_adjust[1].y + var bottom := size.y - top + + var draw_middle : bool = true + if threshold_adjust[0].x >= 0.5: + left = size.x - left + right = size.x - right + draw_middle = false + if threshold_adjust[0].y >= 0.5: + top = size.y - top + bottom = size.y - bottom + draw_middle = false + + _draw_highlight( + left, + top, + right, + bottom, + draw_middle + ) + + if !draw_middle: + if threshold_adjust[0].x >= 0.5: + if threshold_adjust[0].y < 0.5: + draw_line( + Vector2(left, top), + Vector2(right, top), + INTERSECT_HIGHLIGHT_COLOR, + 5 + ) + draw_line( + Vector2(left, bottom), + Vector2(right, bottom), + INTERSECT_HIGHLIGHT_COLOR, + 5 + ) + elif threshold_adjust[0].y >= 0.5: + draw_line( + Vector2(left, top), + Vector2(left, bottom), + INTERSECT_HIGHLIGHT_COLOR, + 5 + ) + draw_line( + Vector2(right, top), + Vector2(right, bottom), + INTERSECT_HIGHLIGHT_COLOR, + 5 + ) +func _draw_highlight( + left : float, + top : float, + right : float, + bottom : float, + draw_middle : bool + ) -> void: + # Middle + if draw_middle: + draw_rect(Rect2(Vector2(left, top), Vector2(right - left, bottom - top)), HIGHLIGHT_COLOR) + return + # Outer + # Left + draw_rect(Rect2(Vector2(0, 0), Vector2(left, size.y)), ANTI_HIGHLIGHT_COLOR) + # Right + draw_rect(Rect2(Vector2(right, 0), Vector2(size.x - right, size.y)), ANTI_HIGHLIGHT_COLOR) + # Top + draw_rect(Rect2(Vector2(left, 0), Vector2(right - left, top)), ANTI_HIGHLIGHT_COLOR) + # Bottom + draw_rect(Rect2(Vector2(left, bottom), Vector2(right - left, size.y - bottom)), ANTI_HIGHLIGHT_COLOR) + +func _scrolled_horizontal(_scroll_hor : float) -> void: + if !(check_mode & CHECK_MODE.HORIZONTAL): return + + var threshold_adjust := _get_threshold_size() + var val : float = is_visible_percent() + + # Checks if visible + if val > 0: + # If visible, but wasn't visible last scroll, then it entered visible area + if !_last_visible: + entered_screen.emit() + _last_visible = true + # Calls the while function + _while_visible(val) + # Else, if visible last frame, then it exited visible area + elif _last_visible: + _while_visible(0) + exited_screen.emit() + _last_visible = false + + val = get_visible_horizontal_percent() + # Checks if in threshold + if val >= threshold_adjust[0].x: + # If in threshold, but not last frame, then it entered threshold area + if _last_threshold_horizontal < threshold_adjust[0].x: + entered_threshold.emit() + # Calls the while function + _while_threshold(val) + # If in threshold, but not last frame, then it entered threshold area + elif _last_threshold_horizontal > threshold_adjust[0].x: + _while_threshold(0) + exited_threshold.emit() + _last_threshold_horizontal = val +func _scrolled_vertical(_scroll_ver : float) -> void: + if !(check_mode & CHECK_MODE.VERTICAL): return + + var threshold_adjust := _get_threshold_size() + var val : float = is_visible_percent() + + # Checks if visible + if val > 0: + # If visible, but wasn't visible last scroll, then it entered visible area + if !_last_visible: + entered_screen.emit() + _last_visible = true + # Calls the while function + _while_visible(val) + # Else, if visible last frame, then it exited visible area + elif _last_visible: + _while_visible(0) + exited_screen.emit() + _last_visible = false + + val = get_visible_vertical_percent() + # Checks if in threshold + if val >= threshold_adjust[0].y: + # If in threshold, but not last frame, then it entered threshold area + if _last_threshold_vertical < threshold_adjust[0].y: + entered_threshold.emit() + # Calls the while function + _while_threshold(val) + # If in threshold, but not last frame, then it entered threshold area + elif _last_threshold_vertical > threshold_adjust[0].y: + _while_threshold(0) + exited_threshold.emit() + _last_threshold_vertical = val + + + +# Public Functions + +## Returns the rect [threshold_horizontal] and [threshold_vertical] create. +func get_threshold_rect(consider_mode : bool = false) -> Rect2: + var threshold_adjust := _get_threshold_size() + return Rect2(threshold_adjust[1], size - threshold_adjust[1]) + + + +# Virtual Functions + +## A virtual function that is called while this node is in the visible area of it's +## scroll. Is called after each scroll of [member scroll]. +## [br][br] +## Paramter [param intersect] is the current visible percent. +func _while_visible(intersect : float) -> void: pass +## A virtual function that is called while this node's visible threshold is met. Is +## called after each scroll of [member scroll]. +## [br][br] +## Paramter [param intersect] is the current threshold value met. +func _while_threshold(intersect : float) -> void: pass + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableZoneControl.gd b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableZoneControl.gd new file mode 100644 index 0000000..daa4116 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/control/AnimatableZoneControl.gd @@ -0,0 +1,386 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name AnimatableZoneControl extends AnimatableScrollControl +## A container to be used for free transformation, within a UI, depended on a +## [ScrollContainer]'s scroll progress. + +## Modes of zone type checking. +enum CHECK_MODE { + NONE = 0b000, ## No behavior. + HORIZONTAL = 0b001, ## Only checks if this node's mount is in the zone horizontally. + VERTICAL = 0b010, ## Only checks if this node's mount is in the zone vertically. + BOTH = 0b011 ## Checks horizontally and vertically. +} + +## Modes of zone size and center position. +enum ZONE_EDITOR_DIMS { + None = 0b00, ## Both horizontal and vertical axis are based on ratio. + Horizontal = 0b01, ## Horizontal axis is based on exact pixel and vertical on ratio. + Vertical = 0b10, ## Horizontal axis is based on ratio and vertical on exact pixel. + Both = 0b11, ## Both horizontal and vertical axis are based on exact pixel. +} + +## Emitted when this node's [AnimatableMount]'s entered the zone area. +signal entered_zone +## Emitted when this node's [AnimatableMount]'s exited the zone area. +signal exited_zone + +## Color for inner highlighting - Indicates when visiblity is required to met threshold. +const HIGHLIGHT_COLOR := Color(Color.RED, 0.3) + +@export_group("Mode") +## Sets the mode of zone checking. +@export var check_mode: CHECK_MODE = CHECK_MODE.NONE: + set(val): + if check_mode != val: + check_mode = val + notify_property_list_changed() + queue_redraw() + +## A flag variable used to distinguish if the center of the zone is described by +## a ratio of the size of the [member AnimatableScrollControl.scroll] value, or +## by a const pixel value.[br] +## Horizontal and vertical axis are consistered differently. +## [br][br] +## See [enum ZONE_EDITOR_DIMS], [member zone_horizontal], and [member zone_vertical]. +var zone_point_pixel : int = 0: + set(val): + if (zone_point_pixel ^ val) & ZONE_EDITOR_DIMS.Horizontal: + _scrolled_horizontal(get_scroll_offset().x) + if (zone_point_pixel ^ val) & ZONE_EDITOR_DIMS.Vertical: + _scrolled_vertical(get_scroll_offset().y) + zone_point_pixel = val + notify_property_list_changed() + queue_redraw() +var _zone_horizontal : float = 0.5 +## The horizontal position of the zone's center, described either as the ratio of +## the size of the [member AnimatableScrollControl.scroll] value, or by a const +## pixel value.[br] +## [br][br] +## See [member zone_point_pixel] +var zone_horizontal : float: + get: return _zone_horizontal + set(val): + if _zone_horizontal != val: + _zone_horizontal = val + _scrolled_horizontal(get_scroll_offset().x) + queue_redraw() +var _zone_vertical : float = 0.5 +## The vertical position of the zone's center, described either as the ratio of +## the size of the [member AnimatableScrollControl.scroll] value, or by a const +## pixel value.[br] +## [br][br] +## See [member zone_point_pixel] +var zone_vertical : float = 0.5: + get: return _zone_vertical + set(val): + if _zone_vertical != val: + _zone_vertical = val + _scrolled_vertical(get_scroll_offset().y) + queue_redraw() + +## A flag variable used to distinguish if the size of the zone is described by +## a ratio of the size of the [member AnimatableScrollControl.scroll] value, or +## by a const pixel value.[br] +## Horizontal and vertical axis are consistered differently. +## [br][br] +## See [enum ZONE_EDITOR_DIMS], [member zone_horizontal], and [member zone_vertical]. +var zone_range_by_pixel : int = 0: + set(val): + if (zone_point_pixel ^ val) & ZONE_EDITOR_DIMS.Horizontal: + _scrolled_horizontal(get_scroll_offset().x) + if (zone_point_pixel ^ val) & ZONE_EDITOR_DIMS.Vertical: + _scrolled_horizontal(get_scroll_offset().y) + zone_range_by_pixel = val + notify_property_list_changed() + queue_redraw() +var _zone_range_horizontal : float = 0.05 +## The horizontal size of the zone, described either as the ratio of the size +## of the [member AnimatableScrollControl.scroll] value, or by a const pixel +## value.[br] +## [br][br] +## See [member zone_vertical] +var zone_range_horizontal : float: + get: return _zone_range_horizontal + set(val): + if _zone_range_horizontal != val: + _zone_range_horizontal = val + _scrolled_horizontal(get_scroll_offset().x) + queue_redraw() +var _zone_range_vertical : float = 0.05 +## The vertical size of the zone, described either as the ratio of the size +## of the [member AnimatableScrollControl.scroll] value, or by a const pixel +## value.[br] +## [br][br] +## See [member zone_vertical] +var zone_range_vertical : float: + get: return _zone_range_vertical + set(val): + if _zone_range_vertical != val: + _zone_range_vertical = val + _scrolled_vertical(get_scroll_offset().y) + queue_redraw() + +## [b]Editor usage only.[/b] Shows or hides the helpful threshold highlighter. +var hide_indicator : bool = true: + set(val): + if hide_indicator != val: + hide_indicator = val + queue_redraw() + +var _last_overlapped : int = 2 + +func _draw() -> void: + if !_mount || !Engine.is_editor_hint() || hide_indicator || !scroll || check_mode == CHECK_MODE.NONE: return + + var draw_rect := get_zone_rect() + var scroll_transform := scroll.get_global_transform() + var transform := _mount.get_global_transform() + + draw_set_transform(scroll_transform.get_origin() - transform.get_origin(), + scroll_transform.get_rotation() - transform.get_rotation(), + scroll_transform.get_scale() / transform.get_scale()) + draw_rect(draw_rect, HIGHLIGHT_COLOR) +func _get_property_list() -> Array[Dictionary]: + var ret : Array[Dictionary] = [] + var horizontal : int = 0 if check_mode & CHECK_MODE.HORIZONTAL else PROPERTY_USAGE_READ_ONLY + var vertical : int = 0 if check_mode & CHECK_MODE.VERTICAL else PROPERTY_USAGE_READ_ONLY + var either : int = horizontal & vertical + + var options : String + if !horizontal: options = "Horizontal:1," + if !vertical: options += "Vertical:2" + + ret.append({ + "name": "Zone Point", + "type": TYPE_NIL, + "usage": PROPERTY_USAGE_GROUP, + "hint_string": "" + }) + ret.append({ + "name": "zone_point_pixel", + "type": TYPE_INT, + "hint": PROPERTY_HINT_FLAGS, + "hint_string": options, + "usage": PROPERTY_USAGE_DEFAULT | either + }) + ret.append({ + "name": "zone_horizontal", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT | horizontal + }.merged({} if zone_point_pixel & 1 else { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,1,0.001,or_less,or_greater" + })) + ret.append({ + "name": "zone_vertical", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT | vertical + }.merged({} if zone_point_pixel & 2 else { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,1,0.001,or_less,or_greater" + })) + + ret.append({ + "name": "Zone Range", + "type": TYPE_NIL, + "usage": PROPERTY_USAGE_GROUP, + "hint_string": "" + }) + ret.append({ + "name": "zone_range_by_pixel", + "type": TYPE_INT, + "hint": PROPERTY_HINT_FLAGS, + "hint_string": options, + "usage": PROPERTY_USAGE_DEFAULT | either + }) + ret.append({ + "name": "zone_range_horizontal", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT | horizontal + }.merged({} if zone_range_by_pixel & 1 else { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,1,0.001,or_less,or_greater" + })) + ret.append({ + "name": "zone_range_vertical", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT | vertical + }.merged({} if zone_range_by_pixel & 2 else { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,1,0.001,or_less,or_greater" + })) + + ret.append({ + "name": "Indicator", + "type": TYPE_NIL, + "usage": PROPERTY_USAGE_GROUP, + "hint_str": "" + }) + ret.append({ + "name": "hide_indicator", + "type": TYPE_BOOL, + "usage": PROPERTY_USAGE_DEFAULT + }) + + return ret +func _property_can_revert(property: StringName) -> bool: + if property in ["zone_point_pixel", "zone_range_by_pixel"]: + if self[property] != 0: return true + elif property in ["zone_horizontal", "zone_vertical"]: + if self[property] != 0.5: return true + elif property in ["zone_range_horizontal", "zone_range_vertical"]: + if self[property] != 0.05: return true + elif property == "hide_indicator": + return !hide_indicator + return false +func _property_get_revert(property: StringName) -> Variant: + if property in ["zone_point_pixel", "zone_range_by_pixel"]: + return 0 + elif property in ["zone_horizontal", "zone_vertical"]: + return 0.5 + elif property in ["zone_range_horizontal", "zone_range_vertical"]: + return 0.05 + elif property == "hide_indicator": + return true + return null + +func _scrolled_horizontal(scroll_hor : float) -> void: + if !(check_mode & CHECK_MODE.HORIZONTAL) || !scroll: return + + var overlapped := is_overlaped_with_activate_zone() + if overlapped: + if _last_overlapped != 1: + entered_zone.emit() + _last_overlapped = 1 + _while_in_zone(zone_local_scroll().x) + elif _last_overlapped: + _last_overlapped = 0 + _while_in_zone(1 if zone_local_scroll().x > 0.5 else 0) + exited_zone.emit() +func _scrolled_vertical(scroll_ver : float) -> void: + if !(check_mode & CHECK_MODE.VERTICAL) || !scroll: return + + var overlapped := is_overlaped_with_activate_zone() + if overlapped: + if _last_overlapped != 1: + entered_zone.emit() + _last_overlapped = 1 + _while_in_zone(zone_local_scroll().y) + elif _last_overlapped: + _last_overlapped = 0 + _while_in_zone(1 if zone_local_scroll().y > 0.5 else 0) + exited_zone.emit() + +func _get_zone_pos() -> Vector2: + var ret := Vector2(zone_horizontal, zone_vertical) + if zone_point_pixel == ZONE_EDITOR_DIMS.None: + ret *= scroll.size + elif zone_point_pixel == ZONE_EDITOR_DIMS.Vertical: + ret.x *= scroll.size.x + elif zone_point_pixel == ZONE_EDITOR_DIMS.Horizontal: + ret.y *= scroll.size.y + return ret +func _get_zone_range() -> Vector2: + var ret := Vector2(zone_range_horizontal, zone_range_vertical) * 0.5 + if zone_range_by_pixel == ZONE_EDITOR_DIMS.None: + ret *= scroll.size + elif zone_range_by_pixel == ZONE_EDITOR_DIMS.Vertical: + ret.x *= scroll.size.x + elif zone_range_by_pixel == ZONE_EDITOR_DIMS.Horizontal: + ret.y *= scroll.size.y + return ret + + + +# Public Functions + +## Returns [code]true[/code] if this node's mount is overlaping the zone area.[br] +## This function's value is dependant on the value of [member check_mode]. +func is_overlaped_with_activate_zone() -> bool: + var item_pos_start := get_origin_offset() + var item_pos_end := item_pos_start + size + + var zone_pos := _get_zone_pos() + var zone_range := _get_zone_range() + var zone_pos_start := zone_pos - zone_range + var zone_pos_end := zone_pos + zone_range + + if (check_mode == CHECK_MODE.VERTICAL): + return (zone_pos_start.y <= item_pos_end.y && zone_pos_end.y >= item_pos_start.y) + elif (check_mode == CHECK_MODE.HORIZONTAL): + return (zone_pos_start.x <= item_pos_end.x && zone_pos_end.x >= item_pos_start.x) + elif (check_mode == CHECK_MODE.BOTH): + return (zone_pos_start.y <= item_pos_end.y && zone_pos_end.y >= item_pos_start.y) && (zone_pos_start.x <= item_pos_end.x && zone_pos_end.x >= item_pos_start.x) + return false + +## Gets the Rect2 associated to the zone. +## [br][br] +## Also see [method get_zone_global_rect], [member zone_horizontal], +## [member zone_vertical], [member zone_range_horizontal], [member zone_range_vertical]. +func get_zone_rect() -> Rect2: + if check_mode == CHECK_MODE.NONE || !scroll: return Rect2() + + var ret : Rect2 = scroll.get_rect() + var zone_pos := _get_zone_pos() + var zone_range := _get_zone_range() + + if (check_mode == CHECK_MODE.VERTICAL): + var pos := zone_pos.y - zone_range.y + var max_pos := max(pos, 0) + + ret.position.y = max_pos + ret.size.y = min(zone_range.y + zone_range.y + pos, scroll.size.y) - max_pos + elif (check_mode == CHECK_MODE.HORIZONTAL): + var pos := zone_pos.x - zone_range.x + var max_pos := max(pos, 0) + + ret.position.x = max_pos + ret.size.x = min(zone_range.x + zone_range.x + pos, scroll.size.x) - max_pos + elif (check_mode == CHECK_MODE.BOTH): + var pos := zone_pos - zone_range + var max_pos := pos.max(Vector2.ZERO) + + ret.position = max_pos + ret.size = scroll.size.min(zone_range + zone_range + pos) - max_pos + return ret +## Gets the global Rect2 associated to the zone. +## [br][br] +## Also see [method get_zone_rect], [member zone_horizontal], [member zone_vertical], +## [member zone_range_horizontal], [member zone_range_vertical]. +func get_zone_global_rect() -> Rect2: + if !scroll: return Rect2() + + var zone_rect := get_zone_rect() + zone_rect.position += scroll.global_position + return zone_rect +## Gets the percentage of this node's mount intersection with the zone. +## [br][br] +## Also see [method get_zone_rect], [method get_zone_global_rect]. +func in_zone_percent() -> float: + if !_mount: return 0 + return (_mount.get_global_rect().intersection(get_zone_global_rect()).get_area()) / (_mount.size.x * _mount.size.y) +## The local scroll within the zone zone. Returns [code]0[/code] if this node's +## mount is not inside the zone area. +## [br][br] +## Also see [method get_zone_rect], [method get_zone_global_rect]. +func zone_local_scroll() -> Vector2: + if !_mount: return Vector2.ZERO + + var zone := get_zone_global_rect() + var mount := _mount.get_global_rect() + + if zone.size + mount.size == Vector2.ZERO: return Vector2.ZERO + return Vector2.ONE + ((zone.position - _mount.global_position - mount.size) / (zone.size + mount.size)).clampf(-1, 0) + + + +# Virtual Functions + +## A virtual function that is called while this node is in the zone area. Is called +## after each scroll of [member scroll]. +## [br][br] +## Paramter [param _scroll] is the local scroll within the zone. +func _while_in_zone(scroll : float) -> void: pass + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/mount/AnimatableMount.gd b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/mount/AnimatableMount.gd new file mode 100644 index 0000000..a7a839a --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/mount/AnimatableMount.gd @@ -0,0 +1,50 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name AnimatableMount extends Control +## Used as a mount for size consistency between children [AnimatableControl] nodes. + +## Emits before children are sorted +signal pre_sort_children +## Emits after children have been sorted +signal sort_children + +var _min_size : Vector2 + +func _get_configuration_warnings() -> PackedStringArray: + for child : Node in get_children(): + if child is AnimatableControl: return [] + return ["This node has no 'AnimatableControl' nodes as children"] +func _get_minimum_size() -> Vector2: + if clip_children: return Vector2.ZERO + _update_children_minimum_size() + return _min_size +func _update_children_minimum_size() -> void: + _min_size = Vector2.ZERO + + # Ensures size is the same as the largest size (of both axis) of children + for child : Node in get_children(): + if child is AnimatableControl: + if child.size_mode & child.SIZE_MODE.MIN: + _min_size = _min_size.max(child.get_combined_minimum_size()) + +func _init() -> void: + resized.connect(_sort_children, CONNECT_DEFERRED) + size_flags_changed.connect(_sort_children, CONNECT_DEFERRED) +func _sort_children() -> void: + pre_sort_children.emit() + for child : Node in get_children(): + if child is AnimatableControl: + child._bound_size() + sort_children.emit() + +## A virtual helper function that should be used when creating your own mounts.[br] +## Is called upon an [AnimatableControl] being added as a child. +func _on_mount(control : AnimatableControl) -> void: pass +## A virtual helper function that should be used when creating your own mounts.[br] +## Is called upon an [AnimatableControl] being removed as a child. +func _on_unmount(control : AnimatableControl) -> void: pass +## A helper function that should be used when creating your own mounts.[br] +## Returns size of this mount. +func get_relative_size(control : AnimatableControl) -> Vector2: return size + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/mount/AnimatableTransformationMount.gd b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/mount/AnimatableTransformationMount.gd new file mode 100644 index 0000000..b305fe0 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/AnimatableControl/mount/AnimatableTransformationMount.gd @@ -0,0 +1,138 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name AnimatableTransformationMount extends AnimatableMount +## An [AnimatableMount] that adjusts for it's children 2D transformations: Rotation, Position, and Scale. + +## If [code]true[/code] this node will adjust it's size to fit its children's scales. +@export var adjust_scale : bool: + set(val): + if val != adjust_scale: + adjust_scale = val + update_minimum_size() +## If [code]true[/code] this node will adjust it's size to fit its children's rotations.[br] +## [b]NOTE[/b]: A large [member pivot_offset] can cause floating point precision issues. +@export var adjust_rotate : bool: + set(val): + if val != adjust_rotate: + adjust_rotate = val + update_minimum_size() +## If [code]true[/code] this node adjust its children's positions inside it's size. +@export var adjust_position : bool: + set(val): + if val != adjust_position: + adjust_position = val + update_minimum_size() + +var _child_min_size : Vector2 + +func _update_children_minimum_size() -> void: + var _old_min_size := _min_size + _min_size = Vector2.ZERO + _child_min_size = Vector2.ZERO + + var children_info: Array[Array] = [] + + for child : AnimatableControl in get_children(): + if child: + var child_size : Vector2 + var child_offset : Vector2 + + # Scale child size, if needed + if adjust_scale: + child_size = child.get_combined_minimum_size() * child.scale + else: + child_size = child.get_combined_minimum_size() + _child_min_size = _child_min_size.max(child_size) + + # Rotates child size, if needed. + if adjust_rotate: + child_offset = child.pivot_offset + if adjust_scale: child_offset *= child.scale + + # Gets the bounding box of the rect, when rotated around a pivot + var bb_rect := _get_rotated_rect_bounding_box( + Rect2(Vector2.ZERO, child_size), + child_offset, + child.rotation + ) + + child_size = bb_rect.size + child_offset = bb_rect.position + + children_info.append([child, child_size, child_offset]) + _min_size = _min_size.max(child_size) + + # Leaves position adjusts after so size, after calculating min-size of children, can be used + if adjust_position: + if _old_min_size != _min_size: + # If in Container, and min_size changed, update after the container as resorted children + if get_parent_control() is Container: + get_parent_control().sort_children.connect(_adjust_children_positions.bind(children_info), CONNECT_ONE_SHOT) + else: + # Otherwise, if min_size changed still, update after minimum_size_changed changed + minimum_size_changed.connect(_adjust_children_positions.bind(children_info), CONNECT_ONE_SHOT | CONNECT_DEFERRED) + update_minimum_size() + else: + # If min_size did not change, deffer children position changes + call_deferred("_adjust_children_positions", children_info) + elif _old_min_size != _min_size: + update_minimum_size() +func _adjust_children_positions(children_info: Array[Array]) -> void: + for child_info : Array in children_info: + var child : AnimatableControl = child_info[0] + var child_size : Vector2 = child_info[1] + var child_offset : Vector2 = child_info[2] + + var piv_offset : Vector2 + # Rotates pivot, if needed + if adjust_rotate: + piv_offset = -child.pivot_offset.rotated(rotation) + else: + piv_offset = -child.pivot_offset + # If adjusts the pivot by scale, if needed + if adjust_scale: + piv_offset *= (child.scale - Vector2.ONE) + + # Not clamp, because min should have priorty + # max_size_adjusted_for_child_size = size - child_size - child_offset - piv_offset + # min_size_adjusted_for_child_size = -piv_offset - child_offset + var new_pos := child.position.min(size - child_size - child_offset - piv_offset).max(-piv_offset - child_offset) + + # Changes position, if needed + if child.position != new_pos: child.position = new_pos + +func _get_rotated_rect_bounding_box(rect : Rect2, pivot : Vector2, angle : float) -> Rect2: + # Base Values + var pos := rect.position + var sze := rect.size + var trig := Vector2(cos(angle), sin(angle)) + + # Simplified equation for centerPoint - bb_size*0.5 + var bb_pos := Vector2( + (sze.x * (trig.x - abs(trig.x)) - sze.y * (trig.y + abs(trig.y))) * 0.5 + pivot.x * (1 - trig.x) + trig.y * pivot.y + pos.x, + (sze.x * (trig.y - abs(trig.y)) + sze.y * (trig.x - abs(trig.x))) * 0.5 + pivot.y * (1 - trig.x) - trig.y * pivot.x + pos.y + ) + trig = trig.abs() + ## Finds the fix of the bounding box of the rotated rectangle + var bb_size := Vector2( + sze.x * trig.x + sze.y * trig.y, + sze.x * trig.y + sze.y * trig.x + ) + + return Rect2(bb_pos, bb_size) + +func _init() -> void: + size_flags_changed.connect(update_minimum_size, CONNECT_DEFERRED) + +func _on_mount(control : AnimatableControl) -> void: + control.transformation_changed.connect(update_minimum_size, CONNECT_DEFERRED) +func _on_unmount(control : AnimatableControl) -> void: + control.transformation_changed.disconnect(update_minimum_size) + +## Returns the adjusted size of this mount. +func get_relative_size(control : AnimatableControl) -> Vector2: + if adjust_scale: + return _child_min_size / control.scale + return _child_min_size + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/AnimatedSwitch.gd b/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/AnimatedSwitch.gd new file mode 100644 index 0000000..c07d40a --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/AnimatedSwitch.gd @@ -0,0 +1,307 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name AnimatedSwitch extends BaseButton +## Animated verison of a switch button. + + + +@export_group("Redirect") +## If [code]true[/code], the switch will be displayed vertical. +@export var vertical : bool = false: + set(val): + if vertical != val: + vertical = val + force_state(button_pressed) +## If [code]true[/code], the switch will be flipped. +@export var flip : bool = false: + set(val): + if flip != val: + flip = val + force_state(button_pressed) + +@export_group("Size") +## The size of the switch's base. +@export var switch_size : Vector2 = Vector2(100, 50): + set(val): + if switch_size != val: + switch_size = val + _handle_resize() + update_minimum_size() +## The size of the switch's knob. +@export var knob_size : Vector2 = Vector2(40, 40): + set(val): + if knob_size != val: + knob_size = val + _handle_resize() + update_minimum_size() +## The base offset of the knob from it's set position. +@export var knob_offset : Vector2 = Vector2.ZERO: + set(val): + if knob_offset != val: + knob_offset = val + _handle_resize() + update_minimum_size() +## An amount of pixels the knob will extend past the switch's base. +## [br][br] +## Also see [member switch_size]. +@export var knob_overextend : float = 10: + set(val): + if knob_overextend != val: + knob_overextend = val + _handle_resize() + update_minimum_size() + +@export_group("Display") +## The style of the switch. +@export var switch_bg : StyleBox: + set(val): + if switch_bg != val: + switch_bg = val + + if knob_bg: + _switch.add_theme_stylebox_override("panel", switch_bg) + else: + _switch.remove_theme_stylebox_override("panel") +## The style of the knob. +@export var knob_bg : StyleBox: + set(val): + if knob_bg != val: + knob_bg = val + + if knob_bg: + _knob.add_theme_stylebox_override("panel", knob_bg) + else: + _knob.remove_theme_stylebox_override("panel") + +@export_group("Colors") +@export_subgroup("Switch") +## The color of the switch's base when unfocused. +@export var switch_bg_normal : Color: + set(val): + if switch_bg_normal != val: + switch_bg_normal = val + + _kill_color_animation() + _animate_color(false) +## The color of the switch's base when focused. +@export var switch_bg_focus : Color: + set(val): + if switch_bg_focus != val: + switch_bg_focus = val + + _kill_color_animation() + _animate_color(false) +## The color of the switch's base when disabled. +@export var switch_bg_disabled : Color: + set(val): + if switch_bg_disabled != val: + switch_bg_disabled = val + + _kill_color_animation() + _animate_color(false) + +@export_subgroup("Knob") +## The color of the switch's knob when unfocused. +@export var knob_bg_normal : Color: + set(val): + if knob_bg_normal != val: + knob_bg_normal = val + + _kill_color_animation() + _animate_color(false) +## The color of the switch's knob when focused. +@export var knob_bg_focus : Color: + set(val): + if knob_bg_focus != val: + knob_bg_focus = val + + _kill_color_animation() + _animate_color(false) +## The color of the switch's knob when disabled. +@export var knob_bg_disabled : Color: + set(val): + if knob_bg_disabled != val: + knob_bg_disabled = val + + _kill_color_animation() + _animate_color(false) + + +@export_group("Animation Properties") +@export_subgroup("Main") +## The ease of the knob's movement across the base. +@export var main_ease : Tween.EaseType +## The transition of the knob's movement across the base. +@export var main_transition : Tween.TransitionType +## The duration of the knob's movement across the base. +@export_range(0.001, 0.5, 0.001, "or_greater") var main_duration : float = 0.15 + +@export_subgroup("Knob Color") +## If [code]true[/code], then the knob will change color according to this node's state. +@export var animate_knob_color : bool = true +## The ease of the knob's color change. +@export var knob_color_ease : Tween.EaseType +## The transition of the knob's color change. +@export var knob_color_transition : Tween.TransitionType +## The duration of the knob's color change. +@export_range(0.001, 0.5, 0.001, "or_greater") var knob_color_duration : float = 0.1 + +@export_subgroup("Switch Color") +## If [code]true[/code], then the base will change color according to this node's state. +@export var animate_switch_color : bool = true +## The ease of the base's color change. +@export var switch_color_ease : Tween.EaseType +## The transition of the base's color change. +@export var switch_color_transition : Tween.TransitionType +## The duration of the base's color change. +@export_range(0.001, 0.5, 0.001, "or_greater") var switch_color_duration : float = 0.1 + + + +var _knob : Panel +var _switch : Panel + +var _main_animate_tween : Tween +var _knob_color_animate_tween : Tween +var _switch_color_animate_tween : Tween + + + +## Instantly changes the switch's state without animation. +func force_state(knob_state : bool) -> void: + _handle_animations(false, knob_state) +## Changes the switch's state with animation. +func toggle_state(knob_state : bool) -> void: + _handle_animations(true, knob_state) + + +## Gets the current knob color. +func get_knob_color() -> Color: + if disabled: + return knob_bg_disabled + return knob_bg_focus if button_pressed else knob_bg_normal +## Gets the current switch base color. +func get_switch_color() -> Color: + if disabled: + return switch_bg_disabled + return switch_bg_focus if button_pressed else switch_bg_normal + + + +func _kill_main_animation() -> void: + if _main_animate_tween && _main_animate_tween.is_running(): + _main_animate_tween.kill() +func _kill_color_animation() -> void: + if _knob_color_animate_tween && _knob_color_animate_tween.is_running(): + _knob_color_animate_tween.kill() + if _switch_color_animate_tween && _switch_color_animate_tween.is_running(): + _main_animate_tween.kill() + + +func _handle_animations(animate : bool, knob_state : bool) -> void: + _kill_main_animation() + _kill_color_animation() + button_pressed = knob_state + + if animate: + _animate_knob(knob_state) + _animate_color(true) + return + + _position_knob(float(knob_state)) + _animate_color(false) + +func _animate_knob(knob_state : bool) -> void: + _main_animate_tween = create_tween() + _main_animate_tween.set_ease(main_ease) + _main_animate_tween.set_trans(main_transition) + _main_animate_tween.tween_method( + _position_knob, + 1.0 - float(knob_state), + 0.0 + float(knob_state), + main_duration + ) +func _position_knob(delta : float) -> void: + if flip: + delta = 1.0 - delta + + var offset : Vector2 + var delta_v : Vector2 + if vertical: + offset = Vector2(0.0, knob_overextend) + delta_v = Vector2(0.5, delta) + else: + offset = Vector2(knob_overextend, 0.0) + delta_v = Vector2(delta, 0.5) + + _knob.position = ( + (switch_size - knob_size + offset + offset) * delta_v # Size + + (_switch.position - offset) # Position + + knob_offset # Offset + ) +func _animate_color(animate : bool = false) -> void: + if animate && animate_knob_color: + _knob_color_animate_tween = create_tween() + _knob_color_animate_tween.set_ease(knob_color_ease) + _knob_color_animate_tween.set_trans(knob_color_transition) + _knob_color_animate_tween.tween_property( + _knob, + "self_modulate", + get_knob_color(), + knob_color_duration + ) + else: + _knob.self_modulate = get_knob_color() + + if animate && animate_switch_color: + _switch_color_animate_tween = create_tween() + _switch_color_animate_tween.set_ease(switch_color_ease) + _switch_color_animate_tween.set_trans(switch_color_transition) + _switch_color_animate_tween.tween_property( + _switch, + "self_modulate", + get_switch_color(), + switch_color_duration + ) + else: + _switch.self_modulate = get_switch_color() + + + +func _init() -> void: + toggle_mode = true + _switch = Panel.new() + _knob = Panel.new() + + _switch.mouse_filter = Control.MOUSE_FILTER_IGNORE + _knob.mouse_filter = Control.MOUSE_FILTER_IGNORE + + add_child(_switch) + add_child(_knob) + + resized.connect(_handle_resize) + toggled.connect(toggle_state) + _handle_resize() + + _animate_color(false) +func _handle_resize() -> void: + _switch.position = (size - switch_size) * 0.5 + _switch.size = switch_size + + _knob.size = knob_size + force_state(button_pressed) + +func _get_minimum_size() -> Vector2: + return (knob_size + (knob_offset.abs() * 0.5)).max(switch_size + Vector2(max(0, knob_overextend) * 2, 0)) +func _validate_property(property: Dictionary) -> void: + if property.name == "toggle_mode": + property.usage &= ~PROPERTY_USAGE_EDITOR + +func _set(property: StringName, value: Variant) -> bool: + if property == "disabled": + disabled = value + _animate_color() + return true + return false + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/HoldButton.gd b/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/HoldButton.gd new file mode 100644 index 0000000..214ed0d --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/HoldButton.gd @@ -0,0 +1,178 @@ +@tool +class_name HoldButton extends Control +## A [Control] node used for hold buttons. + + + +## Emits the state of the button as it is pressed. +## [br][br] +## Also see [member toggle_mode] and [signal button_state]. +signal pressed_state(val : bool) +## Emits the state of the button as it is released. +## [br][br] +## Also see [member toggle_mode] and [signal button_state]. +signal release_state(val : bool) +## Emits the state of the button as it is pressed or released. +## [br][br] +## Also see [signal pressed_state] and [signal release_state]. +signal button_state(val : bool) + + +## Emits when button is released with all vaild conditions. +## [br][br] +## Also see [member release_when_outside] and [member cancel_when_outside]. +signal press_vaild +## Emits when button is released without all vaild conditions. +## [br][br] +## Also see [member release_when_outside] and [member cancel_when_outside]. +signal press_invaild + + +## Emits when press starts. +signal press_start +## Emits when press ends. +signal press_end + + + +## If [code]true[/code], the button's state is pressed. Means the button is pressed down +## or toggled (if [member toggle_mode] is active). Only works if [member toggle_mode] is +## [code]false[/code]. +@export var button_pressed : bool +## If [code]true[/code], the button is in [member toggle_mode]. Makes the button +## flip state between pressed and unpressed each time its area is clicked. +@export var toggle_mode : bool: + set(val): + if toggle_mode != val: + toggle_mode = val + if !val: + button_pressed = false + + notify_property_list_changed() +## If [code]true[/code], then this node does not accept input. +@export var disabled : bool: + set(val): + if disabled != val: + disabled = val + _bounds_check.disabled = val + _distance_check.disabled = val + + +@export_group("Release At") +## If [code]true[/code], the button's held state is released if input moves outside of +## bounds. +@export var release_when_outside : bool = true: + set(val): + release_when_outside = val + _bounds_check.release_when_outside = val +## If [code]true[/code], the button's held state is released and all checking is stopped +## if input moves outside of bounds. +@export var cancel_when_outside : bool = true: + set(val): + cancel_when_outside = val + _bounds_check.cancel_when_outside = val + +@export_group("Release On Drag") +## The current check mode. +## +## Also see [enum CHECK_MODE]. +@export var mode : DistanceCheck.CHECK_MODE = DistanceCheck.CHECK_MODE.BOTH: + set(val): + mode = val + _distance_check.mode = val +## The max pixels difference, between the start and current position, that can be tolerated. +@export var distance : float = 30: + set(val): + distance = val + _distance_check.distance = val + + +var _bounds_check : BoundsCheck +var _distance_check : DistanceCheck + + + +## Forcibly stops this node's check. +func force_release() -> void: + _bounds_check.force_release() + _distance_check.force_release() + + _on_end_invaild() +## Returns if mouse or touch is being held (mouse or touch outside of limit without being released). +## [br][br] +## Also see [method force_release]. +func is_held() -> bool: + return _bounds_check.is_checking() + + + +func _init() -> void: + _distance_check = DistanceCheck.new() + _distance_check.name = "distance_check" + _distance_check.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + add_child(_distance_check) + + _distance_check.mouse_filter = Control.MOUSE_FILTER_IGNORE + _distance_check.cancel_when_outside = true + _distance_check.disabled = disabled + _distance_check.mode = mode + _distance_check.distance = distance + + _distance_check.pos_exceeded.connect(force_release) + + + _bounds_check = BoundsCheck.new() + _bounds_check.name = "bounds_check" + _bounds_check.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + add_child(_bounds_check) + + _bounds_check.mouse_filter = Control.MOUSE_FILTER_IGNORE + _bounds_check.disabled = disabled + _bounds_check.release_when_outside = release_when_outside + _bounds_check.cancel_when_outside = cancel_when_outside + + _bounds_check.end_vaild.connect(_on_end_vaild) + _bounds_check.end_invaild.connect(_on_end_invaild) + _bounds_check.start_check.connect(_on_start_check) + _bounds_check.end_check.connect(_on_end_check) +func _validate_property(property: Dictionary) -> void: + if property.name == "button_pressed": + if !toggle_mode: + property.usage |= PROPERTY_USAGE_READ_ONLY + + +func _gui_input(event: InputEvent) -> void: + if event is InputEventMouseMotion || event is InputEventScreenDrag || event is InputEventMouseButton || event is InputEventScreenTouch: + event.position += global_position + _bounds_check._gui_input(event) + _distance_check._gui_input(event) + + +func _on_start_check() -> void: + press_start.emit() + + if toggle_mode: + button_pressed = !button_pressed + else: + button_pressed = true + button_state.emit(button_pressed) + pressed_state.emit(button_pressed) +func _on_end_check() -> void: + _distance_check.force_release() + press_end.emit() +func _on_end_vaild() -> void: + press_vaild.emit() + + if !toggle_mode: + button_pressed = false + button_state.emit(button_pressed) + release_state.emit(button_pressed) +func _on_end_invaild() -> void: + press_invaild.emit() + + if toggle_mode: + button_pressed = !button_pressed + else: + button_pressed = false + button_state.emit(button_pressed) + release_state.emit(button_pressed) diff --git a/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/MotionCheck/BoundsCheck.gd b/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/MotionCheck/BoundsCheck.gd new file mode 100644 index 0000000..181a887 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/MotionCheck/BoundsCheck.gd @@ -0,0 +1,9 @@ +@tool +class_name BoundsCheck extends MotionCheck +## A [Control] node used to check if a mouse or touch moved outside this node's bounds after +## a vaild press inside. + + + +func _pos_check(pos : Vector2) -> bool: + return get_global_rect().has_point(pos) diff --git a/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/MotionCheck/DistanceCheck.gd b/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/MotionCheck/DistanceCheck.gd new file mode 100644 index 0000000..317b420 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/MotionCheck/DistanceCheck.gd @@ -0,0 +1,46 @@ +@tool +class_name DistanceCheck extends MotionCheck +## A [Control] node used to check if a mouse or touch moved a distance away from where +## the user originally pressed. + + + +## How this node will determine the distance between the starting and current location +## of mouse and touch movement. +enum CHECK_MODE { + NONE = 0b000, ## No action. + HORIZONTAL = 0b001, ## Only checks the horizontal difference. + VERTICAL = 0b010, ## Only checks the vertical difference. + BOTH = 0b011, ## Only checks orthogonal difference. + DISTANCE = 0b100 ## Uses the Pythagorean Theorem. +} + + +## The current check mode. +## +## Also see [enum CHECK_MODE]. +@export var mode : CHECK_MODE +## The max pixels difference, between the start and current position, that can be tolerated. +@export var distance : float = 30 + + + +var _prev_pos : Vector2 + + +func _pos_check(pos : Vector2) -> bool: + if mode & CHECK_MODE.DISTANCE: + if _prev_pos.distance_squared_to(pos) > distance * distance: + return false + return true + + var diff := (_prev_pos - pos).abs() + if mode & CHECK_MODE.HORIZONTAL: + if diff.x > distance: + return false + if mode & CHECK_MODE.VERTICAL: + if diff.y > distance: + return false + return true +func _on_check_start(event: InputEvent) -> void: + _prev_pos = event.position diff --git a/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/MotionCheck/MotionCheck.gd b/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/MotionCheck/MotionCheck.gd new file mode 100644 index 0000000..54e2dcb --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Buttons/Base/MotionCheck/MotionCheck.gd @@ -0,0 +1,165 @@ +@tool +class_name MotionCheck extends Control +## Checks for motion of the mouse or touch input after a press. + +## Emited when check has started (mouse or touch is pressed). +signal start_check +## Emited when check has ended (mouse or touch is released or distance limit has exceeded). +signal end_check + +## Emited when mouse or touch is released within the distence limit. +signal end_vaild +## Emited when check has ended without mouse or touch being released within the distence limit. +## [br][br] +## Also see [member cancel_when_outside] and [method _pos_check]. +signal end_invaild + +## Emited when mouse or touch is moved outside the distence limit. +## [br][br] +## Also see [member cancel_when_outside] and [method _pos_check]. +signal pos_exceeded +## Emited when the current held state changes. +signal held_state(state : bool) + + +## If [code]true[/code], the button's held state is released if input moves outside of +## bounds. +## [br][br] +## Also see [member cancel_when_outside] and [method _pos_check]. +@export var release_when_outside : bool = false: + set(val): + if release_when_outside != val: + release_when_outside = val +## If [code]true[/code], then the check will end when mouse or touch is moved outside the distence limit. +## [br][br] +## Also see [member release_when_outside] and [method _pos_check]. +@export var cancel_when_outside : bool = false: + set(val): + if cancel_when_outside != val: + cancel_when_outside = val +## If [code]true[/code], then this node does not accept input. +@export var disabled : bool: + set(val): + if disabled != val: + disabled = val + if val: + force_release() + + +var _checking : bool = false +var _holding : bool = false + + +# Public Methods + +## Forcibly stops this node's check. +func force_release() -> void: + if _holding: + held_state.emit(false) + _holding = false + if _checking: + _invaild_end() + _checking = false +## Returns if this node is currently checking a mouse or touch press. +func is_checking() -> bool: return _checking +## Returns if mouse or touch is being held (mouse or touch outside of limit without being released). +## [br][br] +## Also see [method force_release]. +func is_held() -> bool: return _holding + + +# Virtual Methods + +## A virtual method that should be overloaded. Returns [code]true[/code] if [param pos] is +## within the distance limit. [code]false[/code] otherwise. +func _pos_check(pos : Vector2) -> bool: + return false +## A virtual method that should be overloaded. This is called when an input starts a +## check. +func _on_check_start(event: InputEvent) -> void: + pass +## A virtual method that should be overloaded. This is called when an input releases a +## check. +func _on_check_release(event: InputEvent) -> void: + pass +## A virtual method that should be overloaded. This is called when an input exceeds a +## check. +## [br][br] +## Also see [method _pos_check]. +func _on_check_exceeded(event: InputEvent) -> void: + pass + + +# Private Methods + +func _init() -> void: + mouse_filter = MOUSE_FILTER_PASS + tree_exiting.connect(force_release) +func _property_can_revert(property: StringName) -> bool: + if property == "mouse_filter": + return mouse_filter == MOUSE_FILTER_PASS + return false +func _property_get_revert(property: StringName) -> Variant: + if property == "mouse_filter": + return MOUSE_FILTER_PASS + return null + + +func _gui_input(event: InputEvent) -> void: + if disabled: return + + if event is InputEventMouseButton || event is InputEventScreenTouch: + if event.pressed: + if !_checking && get_global_rect().has_point(event.position): + _on_check_start(event) + + _checking = true + + held_state.emit(true) + start_check.emit() + elif _checking: + _on_check_release(event) + + _checking = false + if !_holding: + held_state.emit(false) + else: + _holding = false + + if _pos_check(event.position): + _vaild_end() + else: + _invaild_end() + if mouse_filter == MOUSE_FILTER_STOP: accept_event() + + if _checking: + if event is InputEventMouseMotion || event is InputEventScreenDrag: + if _holding: + if _pos_check(event.position): + held_state.emit(true) + _checking = true + _holding = false + return + + if !_pos_check(event.position): + pos_exceeded.emit() + if release_when_outside: + held_state.emit(false) + _holding = true + + if cancel_when_outside: + _on_check_exceeded(event) + _holding = false + _checking = false + + _invaild_end() + return + + if mouse_filter == MOUSE_FILTER_STOP: accept_event() + +func _invaild_end() -> void: + end_invaild.emit() + end_check.emit() +func _vaild_end() -> void: + end_vaild.emit() + end_check.emit() diff --git a/godot/addons/FreeControl/src/CustomClasses/Buttons/Complex/ModulateTransitionButton.gd b/godot/addons/FreeControl/src/CustomClasses/Buttons/Complex/ModulateTransitionButton.gd new file mode 100644 index 0000000..749119d --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Buttons/Complex/ModulateTransitionButton.gd @@ -0,0 +1,151 @@ +@tool +class_name ModulateTransitionButton extends ModulateTransitionContainer +## A button that inherts from [ModulateTransitionContainer] and uses [HoldButton] as +## input. + + + +## Emits the state of the button as it is released. +signal release_state(toggle : bool) + +## Emits when button is released with all vaild conditions. +signal press_vaild + +## Emits when press starts. +signal press_start +## Emits when press ends. +signal press_end + + + +@export_group("Toggleable") +## If [code]true[/code], the button's state is pressed. Means the button is pressed down +## or toggled (if [member toggle_mode] is active). Only works if [member toggle_mode] is +## [code]false[/code]. +@export var button_pressed : bool: + set(val): + if button_pressed != val: + button_pressed = val + _set_button_color(val) +## If [code]true[/code], the button is in [member toggle_mode]. Makes the button +## flip state between pressed and unpressed each time its area is clicked. +@export var toggle_mode : bool: + set(val): + if toggle_mode != val: + toggle_mode = val + button_pressed = false + notify_property_list_changed() + + if _button: _button.toggle_mode = val +## If [code]true[/code], then this node does not accept input. +@export var disabled : bool: + set = _set_disabled + +@export_group("Alpha") +## The color to modulate to when this node is unfocused. +@export var normal_color : Color = Color(1.0, 1.0, 1.0, 1.0): + set(val): + if normal_color != val: + normal_color = val + if is_node_ready(): colors[0] = val + force_color(focused_color) +## The color to modulate to when this node is focused. +@export var focus_color : Color = Color(1.0, 1.0, 1.0, 0.75): + set(val): + if focus_color != val: + focus_color = val + if is_node_ready(): colors[1] = val + force_color(focused_color) +## The color to modulate to when this node is disabled. +@export var disabled_color : Color = Color(1.0, 1.0, 1.0, 0.5): + set(val): + if disabled_color != val: + disabled_color = val + if is_node_ready(): colors[2] = val + force_color(focused_color) + + +var _button : HoldButton + + + +## Forcibly stops this node's check. +func force_release() -> void: + if _button: _button.force_release() +## Returns if mouse or touch is being held (mouse or touch outside of limit without being released). + +func is_held() -> bool: + return _button && _button.is_held() + + + +func _init() -> void: + super() + + _button = HoldButton.new() + add_child(_button) + _button.move_to_front() + + if !Engine.is_editor_hint(): + child_order_changed.connect(_button.move_to_front, CONNECT_DEFERRED) + + _button.button_state.connect(_set_button_color) + _button.release_state.connect(_emit_vaild_release) + + _button.press_start.connect(press_start.emit) + _button.press_end.connect(press_end.emit) + _button.press_vaild.connect(press_vaild.emit) + + _button.mouse_filter = mouse_filter + _button.mouse_force_pass_scroll_events = mouse_force_pass_scroll_events + _button.mouse_default_cursor_shape = mouse_default_cursor_shape + + _button.toggle_mode = toggle_mode + _button.button_pressed = button_pressed + _button.disabled = disabled +func _ready() -> void: + colors = [normal_color, focus_color, disabled_color] + force_color(2 if disabled else (1 if button_pressed else 0)) + +func _validate_property(property: Dictionary) -> void: + match property.name: + "pressed": + if !toggle_mode: + property.usage |= PROPERTY_USAGE_READ_ONLY + "focused_alpha", "alphas": + property.usage &= ~PROPERTY_USAGE_EDITOR + +func _set(property: StringName, value: Variant) -> bool: + if _button: + match property: + "mouse_filter": + _button.mouse_filter = value + "mouse_force_pass_scroll_events": + _button.mouse_force_pass_scroll_events = value + "mouse_default_cursor_shape": + _button.mouse_default_cursor_shape = value + return false +func _get(property: StringName) -> Variant: + if _button: + match property: + "mouse_filter": + return _button.mouse_filter + "mouse_force_pass_scroll_events": + return _button.mouse_force_pass_scroll_events + "mouse_default_cursor_shape": + return _button.mouse_default_cursor_shape + return null + +func _set_disabled(val : bool) -> void: + disabled = val + + _set_button_color(button_pressed) + if _button: + _button.disabled = disabled +func _set_button_color(val : bool) -> void: + if disabled: set_color(2) + else: set_color(int(val)) + +func _emit_vaild_release(release : bool) -> void: + _set_button_color(release) + release_state.emit(release) diff --git a/godot/addons/FreeControl/src/CustomClasses/Buttons/Complex/StyleTransitionButton.gd b/godot/addons/FreeControl/src/CustomClasses/Buttons/Complex/StyleTransitionButton.gd new file mode 100644 index 0000000..ce31ae1 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Buttons/Complex/StyleTransitionButton.gd @@ -0,0 +1,158 @@ +@tool +class_name StyleTransitionButton extends StyleTransitionContainer +## A button that inherts from [StyleTransitionContainer] and uses [HoldButton] as +## input. + + + +## Emits the state of the button as it is released. +signal release_state(toggle : bool) + +## Emits when button is released with all vaild conditions. +signal press_vaild + +## Emits when press starts. +signal press_start +## Emits when press ends. +signal press_end + + + +@export_group("Toggleable") +## If [code]true[/code], the button's state is pressed. Means the button is pressed down +## or toggled (if [member toggle_mode] is active). Only works if [member toggle_mode] is +## [code]false[/code]. +@export var button_pressed : bool: + set(val): + if button_pressed != val: + button_pressed = val + _set_button_color(val) +## If [code]true[/code], the button is in [member toggle_mode]. Makes the button +## flip state between pressed and unpressed each time its area is clicked. +@export var toggle_mode : bool: + set(val): + if toggle_mode != val: + toggle_mode = val + button_pressed = false + notify_property_list_changed() + + if _button: _button.toggle_mode = val +var _disabled : bool: + set = _set_disabled +## If [code]true[/code], then this node does not accept input. +@export var disabled : bool: + set(val): + if disabled != val: + disabled = val + _set_disabled(val) + +@export_group("Colors") +## The color to modulate to when this node is unfocused. +@export var normal_color : Color = Color(0.525, 0.329, 0.808): + set(val): + if normal_color != val: + normal_color = val + if is_node_ready(): colors[0] = val + force_color(focused_color) +## The color to modulate to when this node is focused. +@export var focus_color : Color = Color(0.611, 0.441, 0.886): + set(val): + if focus_color != val: + focus_color = val + if is_node_ready(): colors[1] = val + force_color(focused_color) +## The color to modulate to when this node is disabled. +@export var disabled_color : Color = Color(0.318, 0.247, 0.565): + set(val): + if disabled_color != val: + disabled_color = val + if is_node_ready(): colors[2] = val + force_color(focused_color) + + +var _button : HoldButton + + + +## Forcibly stops this node's check. +func force_release() -> void: + if _button: _button.force_release() +## Returns if mouse or touch is being held (mouse or touch outside of limit without being released). +func is_held() -> bool: + return _button && _button.is_held() + + + +func _init() -> void: + _button = HoldButton.new() + add_child(_button) + + _button.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + _button.move_to_front() + + if !Engine.is_editor_hint(): + child_order_changed.connect(_button.move_to_front, CONNECT_DEFERRED) + + _button.button_state.connect(_set_button_color) + _button.release_state.connect(_emit_vaild_release) + + _button.press_start.connect(press_start.emit) + _button.press_end.connect(press_end.emit) + _button.press_vaild.connect(press_vaild.emit) + + _button.mouse_filter = mouse_filter + _button.mouse_force_pass_scroll_events = mouse_force_pass_scroll_events + _button.mouse_default_cursor_shape = mouse_default_cursor_shape + + _button.toggle_mode = toggle_mode + _button.button_pressed = button_pressed + _button.disabled = _disabled +func _ready() -> void: + super() + colors = [normal_color, focus_color, disabled_color] + force_color(2 if _disabled else (1 if button_pressed else 0)) + + +func _validate_property(property: Dictionary) -> void: + match property.name: + "pressed": + if !toggle_mode: + property.usage |= PROPERTY_USAGE_READ_ONLY + "focused_color", "colors": + property.usage &= ~PROPERTY_USAGE_EDITOR + +func _set(property: StringName, value: Variant) -> bool: + if _button: + match property: + "mouse_filter": + _button.mouse_filter = value + "mouse_force_pass_scroll_events": + _button.mouse_force_pass_scroll_events = value + "mouse_default_cursor_shape": + _button.mouse_default_cursor_shape = value + return false +func _get(property: StringName) -> Variant: + if _button: + match property: + "mouse_filter": + return _button.mouse_filter + "mouse_force_pass_scroll_events": + return _button.mouse_force_pass_scroll_events + "mouse_default_cursor_shape": + return _button.mouse_default_cursor_shape + return null + +func _set_disabled(val : bool) -> void: + _disabled = val || disabled + + _set_button_color(button_pressed) + if _button: + _button.disabled = _disabled +func _set_button_color(val : bool) -> void: + if _disabled: set_color(2) + else: set_color(int(val)) + + +func _emit_vaild_release(release : bool) -> void: + _set_button_color(release) + release_state.emit(release) diff --git a/godot/addons/FreeControl/src/CustomClasses/Carousel/Carousel.gd b/godot/addons/FreeControl/src/CustomClasses/Carousel/Carousel.gd new file mode 100644 index 0000000..512f1ca --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Carousel/Carousel.gd @@ -0,0 +1,616 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name Carousel extends Container +## A container for Carousel Display of [Control] nodes. + + + +## Changes the behavior of how draging scrolls the carousel items. Also see [member snap_carousel_transtion_type], [member snap_carousel_ease_type], and [member paging_requirement]. +enum SNAP_BEHAVIOR { + NONE = 0b00, ## No behavior. + SNAP = 0b01, ## Once drag is released, the carousel will snap to the nearest item.x + PAGING = 0b10 ## Carousel items will not scroll when dragged, unless [member paging_requirement] threshold is met. [member hard_stop] will be assumed as [code]true[/code] for this. +} +## Internel enum used to differentiate what animation is currently playing +enum ANIMATION_TYPE { + NONE = 0b00, ## No behavior. + MANUAL = 0b01, ## Currently animating via request by [method go_to_index]. + SNAP = 0b10 ## Currently animating via an auto-item snapping request. +} + + +## This signal is emited when a snap reaches it's destination. +signal snap_end +## This signal is emited when a snap begins. +signal snap_begin +## This signal is emited when a drag finishes. This does not include the slowdown caused when [member hard_stop] is [code]false[/code]. +signal drag_end +## This signal is emited when a drag begins. +signal drag_begin +## This signal is emited when the slowdown, caused when [member hard_stop] is [code]false[/code], finished naturally. +signal slowdown_end +## This signal is emited when the slowdown, caused when [member hard_stop] is [code]false[/code], is interrupted by another drag or other feature. +signal slowdown_interupted + + + +@export_group("Carousel Options") +## The index of the item this carousel will start at. +@export var starting_index : int = 0: + set(val): + if starting_index != val: + starting_index = val + go_to_index(-val, false) +## The size of each item in the carousel. +@export var item_size : Vector2 = Vector2(200, 200): + set(val): + if item_size != val: + var current_index := get_carousel_index() + item_size = val + _settup_children() + go_to_index(current_index, false) +## The space between each item in the carousel. +@export var item_seperation : int = 0: + set(val): + if item_seperation != val: + item_seperation = val + _kill_animation() + _adjust_children() +## The orientation the carousel items will be displayed in. +@export_range(0, 360, 0.001, "or_less", "or_greater") var carousel_angle : float = 0.0: + set(val): + if carousel_angle != val: + var current_index := get_carousel_index() + + carousel_angle = val + _angle_vec = Vector2.RIGHT.rotated(deg_to_rad(carousel_angle)) + + _kill_animation() + _adjust_children() + go_to_index(current_index, false) + +@export_group("Loop Options") +## Allows looping from the last item to the first and vice versa. +@export var allow_loop : bool = true +## If [code]true[/code], the carousel will display it's items as if looping. Otherwise, the items will not loop. +## [br][br] +## also see [member enforce_border] and [member border_limit]. +@export var display_loop : bool = true: + set(val): + if val != display_loop: + display_loop = val + _adjust_children() + notify_property_list_changed() +## The number of items, surrounding the current item of the current index, that will be visible. +## If [code]-1[/code], all items will be visible. +@export var display_range : int = -1: + set(val): + val = max(-1, val) + if val != display_range: + display_range = val + _adjust_children() + +@export_group("Snap") +## Assigns the behavior of how draging scrolls the carousel items. Also see [member snap_carousel_transtion_type], [member snap_carousel_ease_type], and [member paging_requirement]. +@export var snap_behavior : SNAP_BEHAVIOR = SNAP_BEHAVIOR.SNAP: + set(val): + if val != snap_behavior: + snap_behavior = val + _end_drag_slowdown() + _create_animation(get_carousel_index(), ANIMATION_TYPE.SNAP) + notify_property_list_changed() +## If [member snap_behavior] is [SNAP_BEHAVIOR.PAGING], this is the draging threshold needed to page to the next carousel item. +@export var paging_requirement : int = 200: + set(val): + val = max(1, val) + if val != paging_requirement: + paging_requirement = val + _adjust_children() + +@export_group("Animation Options") +@export_subgroup("Manual") +## The duration of the animation any call to [method go_to_index] will cause, if the animation option is requested. +@export_range(0.001, 2.0, 0.001, "or_greater") var manual_carousel_duration : float = 0.4 +## The [enum Tween.TransitionType] of the animation any call to [method go_to_index] will cause, if the animation option is requested. +@export var manual_carousel_transtion_type : Tween.TransitionType +## The [enum Tween.EaseType] of the animation any call to [method go_to_index] will cause, if the animation option is requested. +@export var manual_carousel_ease_type : Tween.EaseType + +@export_subgroup("Snap") +## The duration of the animation when snapping to an item. +@export_range(0.001, 2.0, 0.001, "or_greater") var snap_carousel_duration : float = 0.2 +## The [enum Tween.TransitionType] of the animation when snapping to an item. +@export var snap_carousel_transtion_type : Tween.TransitionType +## The [enum Tween.EaseType] of the animation when snapping to an item. +@export var snap_carousel_ease_type : Tween.EaseType + +@export_group("Drag") +## If [code]true[/code], the user is allowed to drag via their mouse or touch. +@export var can_drag : bool = true: + set(val): + if val != can_drag: + can_drag = val + notify_property_list_changed() + if !val: + _drag_scroll_value = 0 + if _is_dragging: + _adjust_children() +## If [code]true[/code], the user is allowed to drag outisde the drawer's bounding box. +## [br][br] +## Also see [member can_drag]. +@export var drag_outside : bool = false: + set(val): + if val != drag_outside: + drag_outside = val +@export_subgroup("Limits") +## The max amount a user can drag in either direction. If [code]0[/code], then the user can drag any amount they wish. +@export var drag_limit : int = 0: + set(val): + val = max(0, val) + if val != drag_limit: drag_limit = val +## When dragging, the user will not be able to move past the last or first item, besides for [member border_limit] number of extra pixels. +## [br][br] +## This value is assumed [code]false[/code] is [member display_loop] is [code]true[/code]. +@export var enforce_border : bool = false: + set(val): + if val != enforce_border: + enforce_border = val + _adjust_children() + notify_property_list_changed() +## The amount of extra pixels a user can drag past the last and before the first item in the carousel. +## [br][br] +## This property does nothing if enforce_border is [code]false[/code]. +@export var border_limit : int = 0: + set(val): + if val != border_limit: + border_limit = val + _adjust_children() + +@export_subgroup("Slowdown") +## If [code]true[/code] the carousel will immediately stop when not being dragged. Otherwise, drag speed will be gradually decreased. +## [br][br] +## This property is assumed [code]true[/code] if [member snap_behavior] is set to [SNAP_BEHAVIOR.PAGING]. Also see [member slowdown_drag], [member slowdown_friction], and [member slowdown_cutoff]. +@export var hard_stop : bool = true: + set(val): + if val != hard_stop: + hard_stop = val + _end_drag_slowdown() + notify_property_list_changed() +## The percentage multiplier the drag velocity will experience each frame. +## [br][br] +## This property does nothing if [member hard_stop] is [code]true[/code]. +@export_range(0.0, 1.0, 0.001) var slowdown_drag : float = 0.9 +## The constant decrease the drag velocity will experience each frame. +## [br][br] +## This property does nothing if [member hard_stop] is [code]true[/code]. +@export_range(0.0, 5.0, 0.001, "or_greater", "hide_slider") var slowdown_friction : float = 0.1 +## The cutoff amount. If drag velocity magnitude drops below this amount, the slowdown has finished. +## [br][br] +## This property does nothing if [member hard_stop] is [code]true[/code]. +@export_range(0.01, 10.0, 0.001, "or_greater", "hide_slider") var slowdown_cutoff : float = 0.01 + + +var _scroll_value : int +var _drag_scroll_value : int +var _drag_velocity : float + +var _item_count : int = 0 +var _item_infos : Array + +var _scroll_tween : Tween +var _is_dragging : bool = false + +var _last_animation : ANIMATION_TYPE = ANIMATION_TYPE.NONE + +var _angle_vec : Vector2 +var _mouse_checking : bool + + + + +# Public Functions + +## Gets the index of the current carousel item.[br] +## If [param with_drag] is [code]true[/code] the current drag will also be considered.[br] +## If [param with_clamp] is [code]true[/code] the index will be looped if [member allow_loop] is true or clamped to a vaild index within the carousel. +func get_carousel_index(with_drag : bool = false, with_clamp : bool = true) -> int: + if _item_count == 0: return -1 + + var scroll : int = _scroll_value + if with_drag: scroll += _drag_scroll_value + + var calculated := floori((float(scroll) / float(_get_relevant_axis())) + 0.5) + if with_clamp: + if allow_loop: + calculated = posmod(calculated, _item_count) + else: + calculated = clampi(calculated, 0, _item_count - 1) + + return calculated +## Moves to an item of the given index within the carousel. If an invalid index is given, it will be posmod into a vaild index. +func go_to_index(idx : int, animation : bool = true) -> void: + if _item_count == 0: return + + if allow_loop: + idx = (((idx % _item_count) - _item_count) % _item_count) + else: + idx = clamp(idx, 0, _item_count - 1) + + if animation: + _create_animation(idx, ANIMATION_TYPE.MANUAL) + else: + _kill_animation() + _scroll_value = -_get_relevant_axis() * idx + _adjust_children() +## Moves to the previous item in the carousel, if there is one. +func prev(animation : bool = true) -> void: + go_to_index(get_carousel_index() - 1, animation) +## Moves to the next item in the carousel, if there is one. +func next(animation : bool = true) -> void: + go_to_index(get_carousel_index() + 1, animation) +## Enacts a manual drag on the carousel. This can be used even if [member can_drag] is [code]false[/code]. +## Note that [param from] and [param dir] are considered in local coordinates. +## [br][br] +## Is not affected by [member hard_stop], [member drag_outside], and [member drag_limit]. +func flick(from : Vector2, dir : Vector2) -> void: + drag_begin.emit() + _kill_animation() + _end_drag_slowdown() + + _handle_drag_angle(dir - from) + + _on_drag_release() +## Returns if the carousel is currening scrolling via na animation +func is_animating() -> bool: + return _scroll_tween.is_running() +## Returns if the carousel is currening being dragged by player input. +func being_dragged() -> bool: + return _is_dragging +## Returns the current scroll value. +func get_scroll(with_drag : bool = false) -> int: + if with_drag: + return _scroll_value + _drag_scroll_value + return _scroll_value +## Returns the current number of items in the carousel +func get_item_count() -> int: + return _item_count + + +# Virtual Functions + +## A virtual function that is is called whenever the scroll changes. +func _on_progress(scroll : int) -> void: pass +## A virtual function that is is called whenever the scroll changes, for each visible item in the carousel +func _on_item_progress(item : Control, local_scroll : int, scroll : int, local_index : int, index : int) -> void: pass + + + +func _handle_drag_angle(local_pos : Vector2) -> void: + var projected_scalar : float = -local_pos.dot(_angle_vec) / _angle_vec.length_squared() + _drag_velocity = projected_scalar + _drag_scroll_value += projected_scalar + + if drag_limit != 0: + _drag_scroll_value = clampi(_drag_scroll_value, -drag_limit, drag_limit) + + if snap_behavior == SNAP_BEHAVIOR.PAGING: + if paging_requirement < _drag_scroll_value: + _drag_scroll_value = 0 + var desired := get_carousel_index() + 1 + if allow_loop || desired < _item_count: + _create_animation(desired, ANIMATION_TYPE.SNAP) + elif -paging_requirement > _drag_scroll_value: + _drag_scroll_value = 0 + var desired := get_carousel_index() - 1 + if allow_loop || desired >= 0: + _create_animation(desired, ANIMATION_TYPE.SNAP) + else: + _adjust_children() + + +# Private Functions + +func _get_child_rect(child : Control) -> Rect2: + var child_pos : Vector2 = (size - item_size) * 0.5 + var child_size : Vector2 + + child_size = child.get_combined_minimum_size() + match child.size_flags_horizontal: + SIZE_FILL: child_size.x = item_size.x + SIZE_SHRINK_BEGIN: pass + SIZE_SHRINK_CENTER: child_pos.x += (item_size.x - child_size.x) * 0.5 + SIZE_SHRINK_END: child_pos.x += (item_size.x - child_size.x) + match child.size_flags_vertical: + SIZE_FILL: child_size.y = item_size.y + SIZE_SHRINK_BEGIN: pass + SIZE_SHRINK_CENTER: child_pos.y += (item_size.y - child_size.y) * 0.5 + SIZE_SHRINK_END: child_pos.y += (item_size.y - child_size.y) + + child.size = child_size + child.position = child_pos + + return Rect2(child_pos, child_size) +func _get_control_children() -> Array[Control]: + var ret : Array[Control] + ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible)) + return ret +func _get_relevant_axis() -> int: + var abs_angle_vec = _angle_vec.abs() + + if abs_angle_vec.y >= abs_angle_vec.x: + return (item_size.x / abs_angle_vec.y) + item_seperation + return (item_size.y / abs_angle_vec.x) + item_seperation +func _get_adjusted_scroll() -> int: + var scroll := _scroll_value + if snap_behavior != SNAP_BEHAVIOR.PAGING: + scroll += _drag_scroll_value + if enforce_border: + scroll = clampi(scroll, -border_limit, _get_relevant_axis() * (_item_count - 1) + border_limit) + elif display_loop: + scroll = posmod(scroll, _get_relevant_axis() * _item_count) + return scroll + + +func _create_animation(idx : int, animation_type : ANIMATION_TYPE) -> void: + _kill_animation() + if _item_count == 0: return + _scroll_tween = create_tween() + + var axis := _get_relevant_axis() + var max_scroll := axis * _item_count + if max_scroll == 0: max_scroll = 1 + + var desired_scroll := posmod(axis * idx, max_scroll) + + if allow_loop && display_loop: + _scroll_value = posmod(_scroll_value, max_scroll) + if abs(_scroll_value - desired_scroll) > (max_scroll >> 1): + var left_distance := posmod(_scroll_value - desired_scroll, max_scroll) + var right_distance := posmod(desired_scroll - _scroll_value, max_scroll) + + if left_distance < right_distance: + desired_scroll -= max_scroll + else: + desired_scroll += max_scroll + + _last_animation = animation_type + match animation_type: + ANIMATION_TYPE.MANUAL: + _scroll_tween.set_ease(manual_carousel_ease_type) + _scroll_tween.set_trans(manual_carousel_transtion_type) + _scroll_tween.tween_method( + _animation_method, + _scroll_value, + desired_scroll, + manual_carousel_duration) + ANIMATION_TYPE.SNAP: + snap_begin.emit() + _scroll_tween.set_ease(snap_carousel_ease_type) + _scroll_tween.set_trans(snap_carousel_transtion_type) + _scroll_tween.tween_method( + _animation_method, + _scroll_value, + desired_scroll, + snap_carousel_duration) + _scroll_tween.tween_callback(_kill_animation) + _scroll_tween.play() +func _animation_method(scroll : int) -> void: + _scroll_value = scroll + _adjust_children() +func _kill_animation() -> void: + if _last_animation == ANIMATION_TYPE.SNAP: + snap_end.emit() + _last_animation = ANIMATION_TYPE.NONE + + if _scroll_tween && _scroll_tween.is_running(): + _scroll_tween.kill() + + +func _sort_children() -> void: + _settup_children() + _adjust_children() +func _settup_children() -> void: + var children : Array[Control] = _get_control_children() + _item_count = children.size() + + _item_infos.resize(_item_count) + for i : int in range(0, _item_count): + var item_info := ItemInfo.new() + item_info.node = children[i] + item_info.rect = _get_child_rect(children[i]) + _item_infos[i] = item_info +func _adjust_children() -> void: + if _item_count == 0: return + + var range : Array + var max_local_offset : int + var children : Array[Control] = _get_control_children() + var axis := _get_relevant_axis() + var scroll := _get_adjusted_scroll() + + var index : int + var local_scroll : int + var adjustment : int + + if axis == 0: + index = 0 + local_scroll = 0 + adjustment = 0 + else: + index = int(scroll / axis) + local_scroll = fposmod(scroll, axis) + adjustment = int(scroll < 0) + + index += adjustment & int(local_scroll == 0) + + _on_progress(scroll) + + if display_loop: + if display_range == -1: + max_local_offset = (_item_count >> 1) + range = range(0, _item_count) + for i : int in range: + _item_infos[i].loaded = false + else: + max_local_offset = (display_range >> 1) + range = range(0, (display_range << 1) + 1) + for i : int in range(0, _item_count): + var item_info : ItemInfo = _item_infos[i] + item_info.loaded = false + item_info.node.visible = false + + for item : int in range: + var local_offset := (item >> 1) * (((item & 1) << 1) - 1) + (item & 1) + var local_index := posmod(index + local_offset, _item_count) + var item_info : ItemInfo = _item_infos[local_index] + if item_info.loaded: break + + local_offset += adjustment + var rect : Rect2 = item_info.rect + + rect.position += _angle_vec * (local_offset * axis - local_scroll) + + fit_child_in_rect(item_info.node, rect) + item_info.loaded = true + item_info.node.visible = true + item_info.node.z_index = max_local_offset - abs(local_offset) + _on_item_progress(item_info.node, local_scroll, scroll, item, local_index) + else: + if display_range == -1: + max_local_offset = (_item_count >> 1) + (_item_count & 1) + 1 + range = range(0, _item_count) + else: + max_local_offset = display_range + range = range(max(0, index - display_range), min(_item_count, index + display_range + 1)) + for info : ItemInfo in _item_infos: + info.node.visible = false + + for item : int in range: + var local_index := item - index + var item_info : ItemInfo = _item_infos[item] + + local_index += adjustment + var rect : Rect2 = item_info.rect + + rect.position += _angle_vec * (local_index * axis - local_scroll) + + fit_child_in_rect(item_info.node, rect) + item_info.node.visible = true + item_info.node.z_index = max_local_offset - abs(local_index) + _on_item_progress(item_info.node, local_scroll, scroll, item, local_index) + +func _start_drag_slowdown() -> void: + if is_inside_tree() && !get_tree().process_frame.is_connected(_handle_drag_slowdown): + get_tree().process_frame.connect(_handle_drag_slowdown) +func _end_drag_slowdown() -> void: + if abs(_drag_velocity) < slowdown_cutoff: + slowdown_interupted.emit() + _drag_velocity = 0 + if snap_behavior == SNAP_BEHAVIOR.SNAP: + _create_animation(get_carousel_index(), ANIMATION_TYPE.SNAP) + if is_inside_tree() && get_tree().process_frame.is_connected(_handle_drag_slowdown): + get_tree().process_frame.disconnect(_handle_drag_slowdown) +func _handle_drag_slowdown() -> void: + if abs(_drag_velocity) < slowdown_cutoff: + slowdown_end.emit() + _end_drag_slowdown() + return + + if _drag_velocity > 0: + _drag_velocity = max(0, _drag_velocity - slowdown_friction) + else: + _drag_velocity = min(0, _drag_velocity + slowdown_friction) + _drag_velocity *= slowdown_drag + _scroll_value += _drag_velocity + _adjust_children() + + + +func _init() -> void: + sort_children.connect(_sort_children) + tree_exiting.connect(_end_drag_slowdown) + mouse_exited.connect(_mouse_check) + + _angle_vec = Vector2.RIGHT.rotated(deg_to_rad(carousel_angle)) +func _ready() -> void: + _settup_children() + if _item_count > 0: + starting_index = posmod(starting_index, _item_count) + go_to_index(-starting_index, false) + +func _validate_property(property: Dictionary) -> void: + if property.name == "enforce_border": + if display_loop: + property.usage |= PROPERTY_USAGE_READ_ONLY + elif property.name == "border_limit": + if display_loop || !enforce_border: + property.usage |= PROPERTY_USAGE_READ_ONLY + elif property.name == "paging_requirement": + if snap_behavior != SNAP_BEHAVIOR.PAGING: + property.usage |= PROPERTY_USAGE_READ_ONLY + elif property.name == "hard_stop": + if snap_behavior == SNAP_BEHAVIOR.PAGING: + property.usage |= PROPERTY_USAGE_READ_ONLY + elif property.name in ["slowdown_drag", "slowdown_friction", "slowdown_cutoff"]: + if hard_stop || snap_behavior == SNAP_BEHAVIOR.PAGING: + property.usage |= PROPERTY_USAGE_READ_ONLY + elif property.name in ["drag_outside"]: + if !can_drag: + property.usage |= PROPERTY_USAGE_READ_ONLY + +func _gui_input(event: InputEvent) -> void: + if event is not InputEventMouse: + return + + var has_point := get_viewport_rect().has_point(event.position) + + if ( + (event is InputEventScreenDrag || event is InputEventMouseMotion) && + (drag_outside || has_point) + ): + if event.pressure == 0: + if _is_dragging: _on_drag_release() + return + + if !_is_dragging && has_point: + drag_begin.emit() + _mouse_checking = true + _end_drag_slowdown() + _kill_animation() + _is_dragging = true + + _handle_drag_angle(event.relative) + elif (event is InputEventScreenTouch || event is InputEventMouseButton): + if !event.pressed: _on_drag_release() +func _on_drag_release() -> void: + _mouse_checking = false + _is_dragging = false + drag_end.emit() + + if snap_behavior != SNAP_BEHAVIOR.PAGING: + _scroll_value = _get_adjusted_scroll() + _drag_scroll_value = 0 + if snap_behavior == SNAP_BEHAVIOR.NONE: + if !hard_stop: _start_drag_slowdown() + elif snap_behavior == SNAP_BEHAVIOR.SNAP: + if hard_stop: _create_animation(get_carousel_index(), ANIMATION_TYPE.SNAP) + else: _start_drag_slowdown() + +func _mouse_check() -> void: + if _mouse_checking: + _on_drag_release() + + +func _get_allowed_size_flags_horizontal() -> PackedInt32Array: + return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END] +func _get_allowed_size_flags_vertical() -> PackedInt32Array: + return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END] + + +# Used to hold data about a carousel item +class ItemInfo: + var node : Control + var rect : Rect2 + var loaded : bool + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/CircularContainer/CircularContainer.gd b/godot/addons/FreeControl/src/CustomClasses/CircularContainer/CircularContainer.gd new file mode 100644 index 0000000..07df9fc --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/CircularContainer/CircularContainer.gd @@ -0,0 +1,277 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name CircularContainer extends Container +## A container that positions children in a ellipse within the bounds of this node. + + + +## Behavior the auto angle setter will exhibit. +enum BOUND_BEHAVIOR { + NONE, ## No end bound for angles + STOP, ## Angles, if exceeding the max, will be hard stopped at the max. + LOOP, ## Angles, if exceeding the max, will loop back to the begining. + MIRRIOR ## Angles, if exceeding the max, will bounce back and forth between the min and max angles. +} + + +## The horizontal offset of the ellipse's center. +## [br][br] +## [code]0[/code] is fully left and [code]1[/code] is fully right. +@export_range(0, 1) var origin_x : float = 0.5: + set(val): + origin_x = val + queue_sort() +## The vertical offset of the ellipse's center. +## [br][br] +## [code]0[/code] is fully top and [code]1[/code] is fully bottom. +@export_range(0, 1) var origin_y : float = 0.5: + set(val): + origin_y = val + queue_sort() + +## The horizontal radius of the ellipse's center. +## [br][br] +## [code]0[/code] is [code]0[/code], [code]0.5[/code] is half of [member Control.size].x, and [code]1[/code] is [member Control.size].x. +@export_range(0, 1) var xRadius : float = 0.5: + set(val): + xRadius = val + queue_sort() +## The vertical radius of the ellipse's center. +## [br][br] +## [code]0[/code] is [code]0[/code], [code]0.5[/code] is half of [member Control.size].y, and [code]1[/code] is [member Control.size].y. +@export_range(0, 1) var yRadius : float = 0.5: + set(val): + yRadius = val + queue_sort() + +@export_group("Angles") +## If [code]false[/code], the node will automatically write the angles of children.[br] +## If [code]true[/code], you will be required to manually input angles for each child. +@export var manual : bool = false: + set(val): + if manual != val: + manual = val + notify_property_list_changed() + _calculate_angles() + +## The behavior this node will have if the angle exceeds the max set angle in auto-mode. +## [br][br] +## See [member manual] and [member angle_end], +var bound_behavior : BOUND_BEHAVIOR: + set(val): + if bound_behavior != val: + bound_behavior = val + notify_property_list_changed() + _calculate_angles() +## If true, the nodes will be equal-distantantly placed from each on, between the start and end angles. Overides all other [member bound_behavior]. +## [br][br] +## See [member manual], [member bound_behavior], [member angle_start], and [member angle_end]. +var equal_distant : bool: + set(val): + if equal_distant != val: + equal_distant = val + notify_property_list_changed() + _calculate_angles() + +## The angle auto-mode will increment from. +## [br][br] +## See [member manual], [member bound_behavior], [member angle_start], and [member angle_end]. +var angle_start : float = 0: + set(val): + if angle_start != val: + angle_start = val + _calculate_angles() +## The angle auto-mode will increment with. +## [br][br] +## See [member manual]. +var angle_step : float = 10: + set(val): + if angle_step != val: + angle_step = val + _calculate_angles() +## The angle auto-mode will increment to. +## [br][br] +## See [member manual] and [member bound_behavior]. +var angle_end : float = 360: + set(val): + if angle_end != val: + angle_end = val + _calculate_angles() + +@export_storage var _container_angles : PackedFloat32Array +## The list of angles this ndoe uses to position each child, within the order they are positions in the tree. +var angles : PackedFloat32Array: + set(val): + _container_angles.resize(max(_get_control_children().size(), val.size())) + + for i in range(0, val.size()): + _container_angles[i] = deg_to_rad(val[i]) + for i in range(val.size(), _container_angles.size()): + _container_angles[i] = 0 + + _fix_childrend() + get: + var ret : PackedFloat32Array + ret.resize(_container_angles.size()) + + for i in range(0, _container_angles.size()): + ret[i] = rad_to_deg(_container_angles[i]) + return ret + + + +func _init() -> void: + sort_children.connect(_fix_childrend) + child_order_changed.connect(_childrend_changed) +func _ready() -> void: + _fix_childrend() + +func _childrend_changed() -> void: + _calculate_angles() + _fix_childrend() +func _get_control_children() -> Array[Control]: + var ret : Array[Control] + ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible)) + return ret +func _fix_childrend() -> void: + var children := _get_control_children() + + for index : int in range(0, children.size()): + var child : Control = children[index] + if child: _fix_child(child, index) +func _fix_child(child : Control, index : int) -> void: + if _container_angles.is_empty(): return + child.reset_size() + + # Calculates child position + var child_size := child.get_combined_minimum_size() + var child_pos := -(child_size * 0.5) + (Vector2(origin_x, origin_y) + (Vector2(xRadius, yRadius) * Vector2(cos(_container_angles[index]), sin(_container_angles[index])))) * size + + # Keeps children in the rect's top-left boundards + if child_pos.x < 0: + child_pos.x = 0 + if child_pos.y < 0: + child_pos.y = 0 + + # Keeps children in the rect's bottom-right boundards + if child_pos.x + child_size.x > size.x: + child_pos.x += size.x - (child_pos.x + child_size.x) + if child_pos.y + child_size.y > size.y: + child_pos.y += size.y - (child_pos.y + child_size.y) + if child_pos.y + child_size.y > size.y: + child_size.y = size.y - child_pos.y + + fit_child_in_rect(child, Rect2(child_pos, child_size)) + +func _get_property_list() -> Array[Dictionary]: + var properties : Array[Dictionary] + + if manual: + properties.append({ + "name": "angles", + "type": TYPE_PACKED_FLOAT32_ARRAY, + "usage" : PROPERTY_USAGE_EDITOR + }) + else: + var unbounded = PROPERTY_USAGE_READ_ONLY if bound_behavior == BOUND_BEHAVIOR.NONE else 0 + var allow_step = PROPERTY_USAGE_READ_ONLY if equal_distant && bound_behavior == BOUND_BEHAVIOR.STOP else 0 + + properties.append({ + "name": "bound_behavior", + "type": TYPE_INT, + "hint": PROPERTY_HINT_ENUM, + "hint_string": ",".join(BOUND_BEHAVIOR.keys()), + "usage" : PROPERTY_USAGE_DEFAULT + }) + properties.append({ + "name": "equal_distant", + "type": TYPE_BOOL, + "usage" : PROPERTY_USAGE_DEFAULT + }) + properties.append({ + "name": "Values", + "type": TYPE_NIL, + "usage" : PROPERTY_USAGE_SUBGROUP, + "hint_string": "" + }) + properties.append({ + "name": "angle_start", + "type": TYPE_FLOAT, + "usage" : PROPERTY_USAGE_DEFAULT + }) + properties.append({ + "name": "angle_step", + "type": TYPE_FLOAT, + "usage" : PROPERTY_USAGE_DEFAULT | allow_step + }) + properties.append({ + "name": "angle_end", + "type": TYPE_FLOAT, + "usage" : PROPERTY_USAGE_DEFAULT | unbounded + }) + + return properties +func _property_can_revert(property: StringName) -> bool: + match property: + "bound_behavior": + return bound_behavior != BOUND_BEHAVIOR.NONE + "equal_distant": + return equal_distant + "angle_start": + return angle_start != 0 + "angle_step": + return angle_step != 10 + "angle_end": + return angle_end != 360 + + return false +func _property_get_revert(property: StringName) -> Variant: + match property: + "bound_behavior": + return BOUND_BEHAVIOR.NONE + "equal_distant": + return false + "angle_start": + return 0 + "angle_step": + return 10 + "angle_end": + return 360 + + return null + +func _calculate_angles() -> void: + if manual: return + + var count := _get_control_children().size() + _container_angles.resize(count) + + var start := deg_to_rad(angle_start) + var end := deg_to_rad(angle_end) + + var step : float + if equal_distant: + if count != 0: step = deg_to_rad((angle_end - angle_start) / (count - 1)) + else: + step = deg_to_rad(angle_step) + + var inc_func : Callable + if equal_distant || bound_behavior == BOUND_BEHAVIOR.NONE: + inc_func = func(i : int): return (i * step) + start + elif bound_behavior == BOUND_BEHAVIOR.STOP: + inc_func = func(i : int): return min(i * step, end) + start + elif bound_behavior == BOUND_BEHAVIOR.LOOP: + inc_func = func(i : int): return fmod(i * step, end) + start + elif bound_behavior == BOUND_BEHAVIOR.MIRRIOR: + inc_func = func(i : int): return abs(fmod((i * step) - end, 2 * end) - end) + start + + for i : int in range(0, _container_angles.size()): + _container_angles[i] = fposmod(inc_func.call(i), TAU) + _fix_childrend() + +func _get_allowed_size_flags_horizontal() -> PackedInt32Array: + return [] +func _get_allowed_size_flags_vertical() -> PackedInt32Array: + return [] + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/Drawer/Drawer.gd b/godot/addons/FreeControl/src/CustomClasses/Drawer/Drawer.gd new file mode 100644 index 0000000..15b0cb2 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Drawer/Drawer.gd @@ -0,0 +1,798 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name Drawer extends Container +## A [Container] node used for easy UI Drawers. + +## A flag enum used to classify which input type is allowed. +enum ActionMode { + ACTION_MODE_BUTTON_NONE = 0, ## Allows no input + ACTION_MODE_BUTTON_PRESS = 1, ## Toggles the drawer on tap/click press + ACTION_MODE_BUTTON_RELEASE = 2, ## Toggles the drawer on tap/click release + ACTION_MODE_BUTTON_DRAG = 4, ## Allows user to drag the drawer +} + +## An enum used to classify where input is accepted. +enum InputAreaMode { + Nowhere = 0, ## No input is alloed anywhere on the screen. + Anywhere = 1, ## Input accepted anywere on the screen. + WithinBounds = 2, ## Input is accepted only within this node's rect. + ExcludeDrawer = 3, ## Input is accepted anywhere except on the drawer's rect. + WithinEmptyBounds = 4, ## Input is accepted only within this node's rect, outside of the drawer's rect. +} + +## An enum used to classify when dragging is allowed. +enum DragMode { + NEVER = 0, ## No dragging allowed. + ON_OPEN = 1, ## Dragging is allowed to open the drawer. + ON_CLOSE = 2, ## Dragging is allowed to close the drawer. + ON_OPEN_OR_CLOSE = 0b11 ## Dragging is allowed to open or close the drawer. +} + + +## Emited when drawer is begining an opening/closing animation. +## [br][br] +## Also see: [member state], [method toggle_drawer]. +signal slide_begin +## Emited when drawer is ending an opening/closing animation. +## [br][br] +## Also see: [member state], [method toggle_drawer]. +signal slide_end +## Emited when state has changed, but animation has not began. +## [br][br] +## Also see: [member state], [method toggle_drawer]. +signal state_toggle_begin(toggle : bool) +## Emited when state has changed and animation has finished. +## [br][br] +## Also see: [member state], [method toggle_drawer]. +signal state_toggle_end(toggle : bool) +## Emited when drag has began. +## [br][br] +## Also see: [member allow_drag]. +signal drag_start +## Emited when drag has ended. +## [br][br] +## Also see: [member allow_drag]. +signal drag_end + + + +@export_storage var _state : bool +## The state of the drawer. If [code]true[/code], the drawer is open. Otherwise closed. +## [br][br] +## Also see: [method toggle_drawer]. +var state : bool: + get: return _state + set(val): + if _state != val: + _toggle_drawer(val) + +#@export_group("Drawer Angle") +## The angle in which the drawer will open/close from. +## [br][br] +## Also see: [member drawer_angle_axis_snap]. +var drawer_angle : float = 0.0: + set(val): + if drawer_angle != val: + drawer_angle = val + _angle_vec = Vector2.RIGHT.rotated(deg_to_rad(drawer_angle)) + + _kill_animation() + _calculate_childrend() +## If [code]true[/code], the drawer will be snapped to move as strictly cardinally as possible. +## [br][br] +## Also see: [member drawer_angle]. +var drawer_angle_axis_snap : bool: + set(val): + if drawer_angle_axis_snap != val: + drawer_angle_axis_snap = val + + _kill_animation() + _calculate_childrend() + +#@export_group("Drawer Span") +## If [code]false[/code], [member drawer_width] is equal to a ratio of this node's [Control.size]'s x component. +## [br]. Else, [member drawer_width] is directly editable. +var drawer_width_by_pixel : bool: + set(val): + if val != drawer_width_by_pixel: + drawer_width_by_pixel = val + if val: + drawer_width *= size.x + else: + if size.x == 0: + drawer_width = 0 + else: + drawer_width /= size.x + + notify_property_list_changed() +## The width of the drawer. +## [br][br] +## Also see: [member drawer_width_by_pixel]. +var drawer_width : float = 1: + set(val): + if val != drawer_width: + drawer_width = val + _calculate_childrend() +## If [code]false[/code], [member drawer_height] is equal to a ratio of this node's [Control.size]'s y component. +## [br]. Else, [member drawer_height] is directly editable. +var drawer_height_by_pixel : bool: + set(val): + if val != drawer_height_by_pixel: + drawer_height_by_pixel = val + if val: + drawer_height *= size.y + else: + if size.y == 0: + drawer_height = 0 + else: + drawer_height /= size.y + + notify_property_list_changed() +## The height of the drawer. +## [br][br] +## Also see: [member drawer_height_by_pixel]. +var drawer_height : float = 1: + set(val): + if val != drawer_height: + drawer_height = val + _calculate_childrend() + +#@export_group("Input Options") +## A flag enum used to classify which input type is allowed. +var action_mode : ActionMode = ActionMode.ACTION_MODE_BUTTON_PRESS: + set(val): + if val != action_mode: + action_mode = val + _is_dragging = false + +#@export_subgroup("Margins") +## Extra pixels to where the open drawer lies when open. +var open_margin : int = 0: + set(val): + if val != open_margin: + open_margin = val + _calculate_childrend() +## Extra pixels to where the open drawer lies when closed. +var close_margin : int = 0: + set(val): + if val != close_margin: + close_margin = val + _calculate_childrend() + +#@export_subgroup("Drag Options") +## Permissions on how the user may drag to open/close the drawer. +## [br][br] +## Also see: [member allow_drag], [member smooth_drag]. +var allow_drag : DragMode = DragMode.ON_OPEN_OR_CLOSE +## If [code]true[/code], the drawer will react while the user drags. +var smooth_drag : bool = true +## The amount of extra the user is allowed to drag (in the open direction) before being stopped. +var drag_give : int = 0 + +#@export_subgroup("Open Input") +## A node to determine where vaild input, when closed, may start at. +## [br][br] +## Also see: [member allow_drag]. +var open_bounds : InputAreaMode = InputAreaMode.WithinEmptyBounds +## The minimum amount you need to drag before your drag is considered to have closed the drawer. +## [br][br] +## Also see: [member allow_drag]. +var open_drag_threshold : int = 50: + set(val): + val = max(0, val) + if val != open_drag_threshold: + open_drag_threshold = val + +#@export_subgroup("Close Input") +## A node to determine where vaild input, when open, may start at. +## [br][br] +## Also see: [member allow_drag]. +var close_bounds : InputAreaMode = InputAreaMode.WithinEmptyBounds +## The minimum amount you need to drag before your drag is considered to have opened the drawer. +## [br][br] +## Also see: [member allow_drag]. +var close_drag_threshold : int = 50: + set(val): + val = max(0, val) + if val != close_drag_threshold: + close_drag_threshold = val + +#@export_group("Animation") +#@export_subgroup("Manual Animation") +## The [enum Tween.TransitionType] used when manually opening and closing drawer. +## [br][br] +## Also see: [member state], [method toggle_drawer]. +var manual_drawer_translate : Tween.TransitionType +## The [enum Tween.EaseType] used when manually opening and closing drawer. +## [br][br] +## Also see: [member state], [method toggle_drawer]. +var manual_drawer_ease : Tween.EaseType +## The animation duration used when manually opening and closing drawer. +## [br][br] +## Also see: [member state], [method toggle_drawer]. +var manual_drawer_duration : float = 0.2 + +#@export_subgroup("Drag Animation") +## The [enum Tween.TransitionType] used when snapping after a drag. +var drag_drawer_translate : Tween.TransitionType +## The [enum Tween.EaseType] used when snapping after a drag. +var drag_drawer_ease : Tween.EaseType +## The animation duration used when snapping after a drag. +var drag_drawer_duration : float = 0.2 + + + +var _min_size : Vector2 +var _angle_vec : Vector2 + +var _animation_tween : Tween +var _current_progress : float +var _drag_value : float +var _is_dragging : bool +var _has_dragged : bool + +var _inner_offset : Vector2 +var _outer_offset : Vector2 +var _max_offset : float + + +## Returns if the drawer is currently open. +func is_open() -> bool: + return get_progress_adjusted() > 0.5 +## Returns if the drawer is expected to be open. +func is_open_expected() -> bool: + return _state +## Returns if the drawer is currently animating. +func is_animating() -> bool: + return _animation_tween && _animation_tween.is_running() +## Returns the size of the drawer. +func get_drawer_size() -> Vector2: + var ret := Vector2(drawer_width, drawer_height) + if !drawer_width_by_pixel: + ret.x *= size.x + if !drawer_height_by_pixel: + ret.y *= size.y + return ret.max(_min_size) +## Returns the offsert the drawer has, compared to this node's local position. +func get_drawer_offset(with_drag : bool = false) -> Vector2: + return _get_drawer_offset(_inner_offset, _outer_offset, with_drag) +## Returns the rect the drawer has, compared to this node's local position. +func get_drawer_rect(with_drag : bool = false) -> Rect2: + return Rect2(get_drawer_offset(with_drag), get_drawer_size()) + +## Gets the current progress the drawer is in animations. +## Returns the value in pixel distance. +func get_progress(include_drag : bool = false, with_clamp : bool = true) -> float: + var ret : float = _current_progress + if include_drag: + ret += _drag_value + if with_clamp: + ret = clampf(ret, -drag_give, _max_offset) + return ret +## Gets the percentage of the drawer's current position between being closed and opened. +## [code]0.0[/code] when closed and [code]1.0[/code] when opened. +func get_progress_adjusted(include_drag : bool = false, with_clamp : bool = true) -> float: + if _max_offset == 0.0: return 0.0 + return get_progress(include_drag, with_clamp) / _max_offset + + + +func _get_relevant_axis() -> float: + var drawer_size := get_drawer_size() + var abs_angle_vec = _angle_vec.abs() + + if abs_angle_vec.y >= abs_angle_vec.x: + return (drawer_size.x / abs_angle_vec.y) + return (drawer_size.y / abs_angle_vec.x) +func _get_control_children() -> Array[Control]: + var ret : Array[Control] + ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible)) + return ret +func _calculate_childrend() -> void: + _find_offsets() + _current_progress = _max_offset * float(_state) + _adjust_children() +func _adjust_children() -> void: + var rect := get_drawer_rect(true) + for child : Control in _get_control_children(): + fit_child_in_rect(child, rect) + + + +func _find_minimum_size() -> Vector2: + var min_size : Vector2 = Vector2.ZERO + for child : Control in _get_control_children(): + min_size = min_size.max(child.get_combined_minimum_size()) + return min_size +func _find_offsets() -> void: + var drawer_size := get_drawer_size() + + var distances_to_intersection_point := (size / _angle_vec).abs() + var inner_distance := minf(distances_to_intersection_point.x, distances_to_intersection_point.y) + var inner_point : Vector2 = (inner_distance * _angle_vec + (size - drawer_size)) * 0.5 + _inner_offset = inner_point.maxf(0).min(size - drawer_size) + + if drawer_angle_axis_snap: + var half_drawer_size := drawer_size * 0.5 + var inner_point_half := inner_point + half_drawer_size + _outer_offset = inner_point + + if abs(inner_point_half.x - size.x) < 0.01: + _outer_offset.x += half_drawer_size.x + elif abs(inner_point_half.x) < 0.01: + _outer_offset.x -= half_drawer_size.x + + if abs(inner_point_half.y - size.y) < 0.01: + _outer_offset.y += half_drawer_size.y + elif abs(inner_point_half.y) < 0.01: + _outer_offset.y -= half_drawer_size.y + else: + var distances_to_outer_center := ((size + drawer_size) / _angle_vec).abs() + var outer_distance := minf(distances_to_outer_center.x, distances_to_outer_center.y) + _outer_offset = (outer_distance * _angle_vec + (size - drawer_size)) * 0.5 + + _max_offset = (_outer_offset - _inner_offset).length() + _inner_offset = (_inner_offset + _angle_vec * open_margin).floor() + _outer_offset = (_outer_offset - _angle_vec * close_margin).floor() + + +## Allows opening and closing the drawer. +## [br][br] +## Also see: [member state] and [method force_drawer]. +func toggle_drawer(open : bool) -> void: + _toggle_drawer(open) +func _toggle_drawer(open : bool, drag_animate : bool = false) -> void: + slide_begin.emit() + _animate_to_progress(float(open), drag_animate) + _animation_tween.tween_callback(slide_end.emit) + + if _state != open: + state_toggle_begin.emit(open) + _animation_tween.tween_callback(state_toggle_end.emit.bind(open)) + _state = open +func _animate_to_progress( + to_progress : float, + drag_animate : bool = false + ) -> void: + _kill_animation() + _animation_tween = create_tween() + + if drag_animate: + _animation_tween.set_trans(drag_drawer_translate) + _animation_tween.set_ease(drag_drawer_ease) + _animation_tween.tween_method( + _animation_method, + get_progress(true), + to_progress * _max_offset, + drag_drawer_duration + ) + else: + _animation_tween.set_trans(manual_drawer_translate) + _animation_tween.set_ease(manual_drawer_ease) + _animation_tween.tween_method( + _animation_method, + get_progress(true), + to_progress * _max_offset, + manual_drawer_duration + ) +func _kill_animation() -> void: + if _animation_tween && _animation_tween.is_running(): + _animation_tween.kill() +func _animation_method(progress : float) -> void: + _current_progress = progress + _progress_changed(get_progress_adjusted()) + _adjust_children() + + + +## Allows opening and closing the drawer without animation. +## [br][br] +## Also see: [member state] and [method toggle_drawer]. +func force_drawer(open : bool) -> void: + _kill_animation() + _state = open + _animation_method(float(open) * _max_offset) + + + +func _init() -> void: + resized.connect(_calculate_childrend) + sort_children.connect(_calculate_childrend) + + _angle_vec = Vector2.RIGHT.rotated(deg_to_rad(drawer_angle)) +func _get_minimum_size() -> Vector2: + _min_size = _find_minimum_size() + return _min_size +func _get_property_list() -> Array[Dictionary]: + var ret : Array[Dictionary] + + ret.append({ + "name": "state", + "type": TYPE_BOOL, + "usage": PROPERTY_USAGE_EDITOR + }) + + + ret.append({ + "name": "Drawer Angle", + "type": TYPE_NIL, + "hint_string": "drawer_", + "usage": PROPERTY_USAGE_GROUP + }) + ret.append({ + "name": "drawer_angle", + "type": TYPE_FLOAT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0, 360, 0.001, or_less, or_greater", + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "drawer_angle_axis_snap", + "type": TYPE_BOOL, + "usage": PROPERTY_USAGE_DEFAULT + }) + + + ret.append({ + "name": "Drawer Span", + "type": TYPE_NIL, + "hint_string": "drawer_", + "usage": PROPERTY_USAGE_GROUP + }) + ret.append({ + "name": "drawer_width_by_pixel", + "type": TYPE_BOOL, + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "drawer_width", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT + }.merged({} if drawer_width_by_pixel else { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0, 1, 0.001, or_less, or_greater", + })) + ret.append({ + "name": "drawer_height_by_pixel", + "type": TYPE_BOOL, + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "drawer_height", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT + }.merged({} if drawer_height_by_pixel else { + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0, 1, 0.001, or_less, or_greater", + })) + + ret.append({ + "name": "Input Options", + "type": TYPE_NIL, + "usage": PROPERTY_USAGE_GROUP + }) + + ret.append({ + "name": "action_mode", + "type": TYPE_INT, + "hint": PROPERTY_HINT_FLAGS, + "hint_string": "Press Action:1,Release Action:2,Drag Action:4", + "usage": PROPERTY_USAGE_DEFAULT + }) + + ret.append({ + "name": "Margins", + "type": TYPE_NIL, + "usage": PROPERTY_USAGE_SUBGROUP + }) + ret.append({ + "name": "open_margin", + "type": TYPE_INT, + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "close_margin", + "type": TYPE_INT, + "usage": PROPERTY_USAGE_DEFAULT + }) + + ret.append({ + "name": "Drag Options", + "type": TYPE_NIL, + "usage": PROPERTY_USAGE_SUBGROUP + }) + ret.append({ + "name": "allow_drag", + "type": TYPE_INT, + "hint": PROPERTY_HINT_ENUM, + "hint_string": _convert_to_enum(DragMode.keys()), + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "smooth_drag", + "type": TYPE_BOOL, + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "drag_give", + "type": TYPE_INT, + "usage": PROPERTY_USAGE_DEFAULT + }) + + ret.append({ + "name": "Open Input", + "type": TYPE_NIL, + "hint_string": "", + "usage": PROPERTY_USAGE_SUBGROUP + }) + ret.append({ + "name": "open_bounds", + "type": TYPE_INT, + "hint": PROPERTY_HINT_ENUM, + "hint_string": _convert_to_enum(InputAreaMode.keys()), + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "open_drag_threshold", + "type": TYPE_INT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0, 1, 1, or_greater", + "usage": PROPERTY_USAGE_DEFAULT + }) + + ret.append({ + "name": "Close Input", + "type": TYPE_NIL, + "hint_string": "", + "usage": PROPERTY_USAGE_SUBGROUP + }) + ret.append({ + "name": "close_bounds", + "type": TYPE_INT, + "hint": PROPERTY_HINT_ENUM, + "hint_string": _convert_to_enum(InputAreaMode.keys()), + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "close_drag_threshold", + "type": TYPE_INT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0, 1, 1, or_greater", + "usage": PROPERTY_USAGE_DEFAULT + }) + + + ret.append({ + "name": "Animation", + "type": TYPE_NIL, + "usage": PROPERTY_USAGE_GROUP + }) + + ret.append({ + "name": "Manual Animation", + "type": TYPE_NIL, + "hint_string": "manual_drawer_", + "usage": PROPERTY_USAGE_SUBGROUP + }) + ret.append({ + "name": "manual_drawer_translate", + "type": TYPE_INT, + "hint": PROPERTY_HINT_ENUM, + "hint_string": _get_enum_string("Tween", "TransitionType"), + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "manual_drawer_ease", + "type": TYPE_INT, + "hint": PROPERTY_HINT_ENUM, + "hint_string": _get_enum_string("Tween", "EaseType"), + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "manual_drawer_duration", + "type": TYPE_FLOAT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0, 1, 0.001, or_greater", + "usage": PROPERTY_USAGE_DEFAULT + }) + + ret.append({ + "name": "Drag Animation", + "type": TYPE_NIL, + "hint_string": "drag_drawer_", + "usage": PROPERTY_USAGE_SUBGROUP + }) + ret.append({ + "name": "drag_drawer_translate", + "type": TYPE_INT, + "hint": PROPERTY_HINT_ENUM, + "hint_string": _get_enum_string("Tween", "TransitionType"), + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "drag_drawer_ease", + "type": TYPE_INT, + "hint": PROPERTY_HINT_ENUM, + "hint_string": _get_enum_string("Tween", "EaseType"), + "usage": PROPERTY_USAGE_DEFAULT + }) + ret.append({ + "name": "drag_drawer_duration", + "type": TYPE_FLOAT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0, 1, 0.001, or_greater", + "usage": PROPERTY_USAGE_DEFAULT + }) + + return ret +func _property_can_revert(property: StringName) -> bool: + return property in [ + "state", + "drawer_angle", + "drawer_angle_axis_snap", + "drawer_width_by_pixel", + "drawer_width", + "drawer_height_by_pixel", + "drawer_height", + "action_mode", + "drag_give", + "open_margin", + "close_margin", + "allow_drag", + "smooth_drag", + "open_bounds", + "open_drag_threshold", + "close_bounds", + "close_drag_threshold", + "manual_drawer_translate", + "manual_drawer_ease", + "manual_drawer_duration", + "drag_drawer_translate", + "drag_drawer_ease", + "drag_drawer_duration" + ] +func _property_get_revert(property: StringName) -> Variant: + match property: + "smooth_drag": + return true + "state", "drawer_width_by_pixel", "drawer_height_by_pixel", "drawer_angle_axis_snap": + return false + + "drag_give", "open_margin", "close_margin": + return 0 + "drawer_angle": + return 0.0 + "manual_drawer_duration", "drag_drawer_duration": + return 0.2 + "open_drag_threshold": + return 50 + "close_drag_threshold": + return 50 + + "drawer_width": + return size.x if drawer_width_by_pixel else 1.0 + "drawer_height": + return size.y if drawer_height_by_pixel else 1.0 + + "action_mode": + return ActionMode.ACTION_MODE_BUTTON_PRESS + "allow_drag": + return DragMode.ON_OPEN_OR_CLOSE + "open_bounds": + return InputAreaMode.WithinEmptyBounds + "close_bounds": + return InputAreaMode.WithinEmptyBounds + + "manual_drawer_translate", "drag_drawer_translate": + return Tween.TransitionType.TRANS_LINEAR + "manual_drawer_ease", "drag_drawer_ease": + return Tween.EaseType.EASE_IN + return null +func _get_enum_string(className : StringName, enumName : StringName) -> String: + var ret : String + for constant_name in ClassDB.class_get_enum_constants(className, enumName): + var constant_value: int = ClassDB.class_get_integer_constant(className, constant_name) + ret += "%s:%d, " % [constant_name, constant_value] + return ret.left(-2).replace("_", " ").capitalize().replace(", ", ",") +func _convert_to_enum(strs : PackedStringArray) -> String: + return ", ".join(strs).replace("_", " ").capitalize().replace(", ", ",") + + + +func _confirm_input_accept(event : InputEvent, drag : bool = false) -> bool: + if mouse_filter == MouseFilter.MOUSE_FILTER_IGNORE: return false + + var boundType : InputAreaMode + if _state: + boundType = close_bounds + if drag && !(allow_drag & DragMode.ON_CLOSE): + return false + else: + boundType = open_bounds + if drag && !(allow_drag & DragMode.ON_OPEN): + return false + + match boundType: + InputAreaMode.Nowhere: + return false + InputAreaMode.Anywhere: + pass + InputAreaMode.WithinBounds: + if !get_rect().has_point(event.position): + return false + InputAreaMode.ExcludeDrawer: + if get_drawer_rect().has_point(event.position): + if mouse_filter == MouseFilter.MOUSE_FILTER_STOP: + get_viewport().set_input_as_handled() + return false + InputAreaMode.WithinEmptyBounds: + if get_rect().intersection(get_drawer_rect()).has_point(event.position): + if mouse_filter == MouseFilter.MOUSE_FILTER_STOP: + get_viewport().set_input_as_handled() + return false + + if mouse_filter == MouseFilter.MOUSE_FILTER_STOP: + get_viewport().set_input_as_handled() + return true +func _gui_input(event: InputEvent) -> void: + if event is InputEventMouseButton || event is InputEventScreenTouch: + if event.pressed: + if !_is_dragging: + if action_mode & ActionMode.ACTION_MODE_BUTTON_PRESS: + if !_confirm_input_accept(event, false): return + _toggle_drawer(!is_open()) + return + + if action_mode & ActionMode.ACTION_MODE_BUTTON_DRAG: + if !_confirm_input_accept(event, true): return + drag_start.emit() + _is_dragging = true + else: + if action_mode & ActionMode.ACTION_MODE_BUTTON_RELEASE: + if _confirm_input_accept(event, false) && !_has_dragged: + _toggle_drawer(!is_open()) + + _has_dragged = false + _is_dragging = false + _drag_value = 0.0 + return + + if action_mode & ActionMode.ACTION_MODE_BUTTON_DRAG: + if _is_dragging: + drag_end.emit() + if is_open(): + _toggle_drawer(_drag_value > -open_drag_threshold, true) + else: + _toggle_drawer(_drag_value > close_drag_threshold, true) + + set_deferred("_has_dragged", false) + set_deferred("_is_dragging", false) + set_deferred("_drag_value", 0.0) + return + + if _is_dragging: + if event is InputEventMouseMotion || event is InputEventScreenDrag: + var projected_scalar : float = event.relative.dot(_angle_vec) / _angle_vec.length_squared() + _drag_value += projected_scalar + + if is_zero_approx(_drag_value): + _has_dragged = true + + _progress_changed(get_progress_adjusted(true)) + if smooth_drag: _adjust_children() + + + +# Overload Functions +## Used by [method get_drawer_offset] to calculate the offset of the drawer, given the current progress. +## Overload this method to create custom opening/closing behavior. +func _get_drawer_offset(inner_offset : Vector2, outer_offset : Vector2, with_drag : bool = false) -> Vector2: + #return (outer_offset - inner_offset) * get_progress_adjusted(with_drag) + inner_offset + return inner_offset.lerp(outer_offset, get_progress_adjusted(with_drag)) + +# Virtual Functions + +## A virtual function that is is called whenever the drawer progress changes. +func _progress_changed(progress : float) -> void: pass + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/PaddingContainer/PaddingContainer.gd b/godot/addons/FreeControl/src/CustomClasses/PaddingContainer/PaddingContainer.gd new file mode 100644 index 0000000..6f91fb7 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/PaddingContainer/PaddingContainer.gd @@ -0,0 +1,222 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name PaddingContainer extends Container +## A [Container] that provides percentage and numerical padding to it's children. + + +## If [code]true[/code], this [Container]'s minimum size will update according to it's +## children and numerical pixel padding. +@export var minimum_size : bool = true: + set(val): + if minimum_size != val: + minimum_size = val + + update_minimum_size() + +## The percentage left padding. +var child_anchor_left : float = 0: + set(val): + if child_anchor_left != val: + child_anchor_left = val + + child_anchor_right = max(val, child_anchor_right) +## The percentage top padding. +var child_anchor_top : float = 0: + set(val): + if child_anchor_top != val: + child_anchor_top = val + + child_anchor_bottom = max(val, child_anchor_bottom) +## The percentage right padding. +var child_anchor_right : float = 1: + set(val): + if child_anchor_right != val: + child_anchor_right = val + + child_anchor_left = min(val, child_anchor_left) +## The percentage bottom padding. +var child_anchor_bottom : float = 1: + set(val): + if child_anchor_bottom != val: + child_anchor_bottom = val + + child_anchor_top = min(val, child_anchor_top) + +## The numerical pixel left padding. +var child_offset_left : int = 0: + set(val): + if child_offset_left != val: + child_offset_left = val + + update_minimum_size() +## The numerical pixel top padding. +var child_offset_top : int = 0: + set(val): + if child_offset_top != val: + child_offset_top = val + + update_minimum_size() +## The numerical pixel right padding. +var child_offset_right : int = 0: + set(val): + if child_offset_right != val: + child_offset_right = val + + update_minimum_size() +## The numerical pixel bottom padding. +var child_offset_bottom : int = 0: + set(val): + if child_offset_bottom != val: + child_offset_bottom = val + + update_minimum_size() + + +func _init() -> void: + sort_children.connect(_handel_resize) +func _ready() -> void: + _handel_resize() +func _get_property_list() -> Array[Dictionary]: + var properties : Array[Dictionary] = [] + + properties.append({ + "name" = "Anchors", + "type" = TYPE_NIL, + "usage" = PROPERTY_USAGE_GROUP, + "hint_string" = "child_anchor_" + }) + properties.append({ + "name": "child_anchor_left", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,1,0.001,or_greater,or_less" + }) + properties.append({ + "name": "child_anchor_top", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,1,0.001,or_greater,or_less" + }) + properties.append({ + "name": "child_anchor_right", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,1,0.001,or_greater,or_less" + }) + properties.append({ + "name": "child_anchor_bottom", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT, + "hint": PROPERTY_HINT_RANGE, + "hint_string": "0,1,0.001,or_greater,or_less" + }) + + properties.append({ + "name" = "Offsets", + "type" = TYPE_NIL, + "usage" = PROPERTY_USAGE_GROUP, + "hint_string" = "child_offset_" + }) + properties.append({ + "name": "child_offset_left", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT + }) + properties.append({ + "name": "child_offset_top", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT + }) + properties.append({ + "name": "child_offset_right", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT + }) + properties.append({ + "name": "child_offset_bottom", + "type": TYPE_FLOAT, + "usage": PROPERTY_USAGE_DEFAULT + }) + + return properties +func _property_can_revert(property: StringName) -> bool: + if property in [ + "child_anchor_left", + "child_anchor_top", + ]: + return self[property] != 0.0 + elif property in [ + "child_anchor_right", + "child_anchor_bottom", + ]: + return self[property] != 1.0 + elif property in [ + "child_offset_left", + "child_offset_top", + "child_offset_right", + "child_offset_bottom" + ]: + return self[property] != 0 + return false +func _property_get_revert(property: StringName) -> Variant: + if property in [ + "child_anchor_left", + "child_anchor_top", + ]: + return 0.0 + elif property in [ + "child_anchor_right", + "child_anchor_bottom", + ]: + return 1.0 + elif property in [ + "child_offset_left", + "child_offset_top", + "child_offset_right", + "child_offset_bottom" + ]: + return 0 + return null + +func _get_minimum_size() -> Vector2: + if !minimum_size: return Vector2.ZERO + + var min : Vector2 + for child : Node in get_children(): + if child is Control: + min = min.max(child.get_combined_minimum_size()) + + min += Vector2( + child_offset_left + child_offset_right, + child_offset_top + child_offset_bottom + ) + + return min + +func _handel_resize() -> void: + for child in get_children(): + if child is Control: + child.set_anchor_and_offset( + SIDE_LEFT, + child_anchor_left, + child_offset_left + ) + child.set_anchor_and_offset( + SIDE_TOP, + child_anchor_top, + child_offset_top + ) + child.set_anchor_and_offset( + SIDE_RIGHT, + child_anchor_right, + -child_offset_right + ) + child.set_anchor_and_offset( + SIDE_BOTTOM, + child_anchor_bottom, + -child_offset_bottom + ) +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/ProportionalContainer/ProportionalContainer.gd b/godot/addons/FreeControl/src/CustomClasses/ProportionalContainer/ProportionalContainer.gd new file mode 100644 index 0000000..cb8d0e0 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/ProportionalContainer/ProportionalContainer.gd @@ -0,0 +1,188 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name ProportionalContainer extends Container +## A container that preserves the proportions of its [member ancher] size. +## [br][br] +## [b]WARNING[b]: Is this can cause crashes if misused. Try to use [PaddingContainer] instead, unless required. + + +## The method this node will change in proportion of its [member ancher] size. +enum PROPORTION_MODE { + NONE = 0b000, ## No action. Minimum size will be set at [constant Vector2.ZERO]. + WIDTH = 0b101, ## Same as [WIDTH_PROPORTION], but also sets children height to be equal to the [member ancher]'s size's height. + WIDTH_PROPORTION = 0b001, ## Sets the minimum width to be equal to the [member ancher] width multipled by [member horizontal_ratio]. + HEIGHT = 0b110, ## Same as [HEIGHT_PROPORTION], but also sets children height to be equal to the [member ancher]'s size's width. + HEIGHT_PROPORTION = 0b010, ## Sets the minimum height to be equal to the [member ancher] height multipled by [member vertical_ratio]. + BOTH = 0b011 ## Sets the minimum size to be equal to the [member ancher] size multipled by [member horizontal_ratio] and [member vertical_ratio] respectively. +} + + + +@export_group("Ancher") +## The ancher node this container proportions itself to. Is used if [member ancher_to_parent] is [code]false[/code]. +## [br][br] +## If [code]null[/code], then this container proportions itself to it's parent control size. +@export var ancher : Control: + set(val): + if ancher != val: + if ancher && ancher.resized.is_connected(_sort_children): + ancher.resized.disconnect(_sort_children) + if val && !val.resized.is_connected(_sort_children): + val.resized.connect(_sort_children) + ancher = val + queue_sort() + +@export_group("Proportion") +## The proportion mode used to scale itself to the [member ancher]. +@export var mode : PROPORTION_MODE = PROPORTION_MODE.NONE: + set(val): + if mode != val: + mode = val + notify_property_list_changed() + queue_sort() +## The multiplicative of this node's width to the [member ancher] width. +@export_range(0., 1., 0.001, "or_greater") var horizontal_ratio : float = 1.: + set(val): + if horizontal_ratio != val: + horizontal_ratio = val + queue_sort() +## The multiplicative of this node's height to the [member ancher] height. +@export_range(0., 1., 0.001, "or_greater") var vertical_ratio : float = 1.: + set(val): + if vertical_ratio != val: + vertical_ratio = val + queue_sort() + + + +var _min_size : Vector2 +var _ignore_resize : bool + + + +func _init() -> void: + layout_mode = 0 + sort_children.connect(_sort_children) +func _ready() -> void: + _sort_children() + +func _validate_property(property: Dictionary) -> void: + if property.name in [ + "layout_mode", + "size", + ]: + property.usage |= PROPERTY_USAGE_READ_ONLY + elif property.name == "horizontal_ratio": + if !(mode & PROPORTION_MODE.WIDTH_PROPORTION): + property.usage |= PROPERTY_USAGE_READ_ONLY + elif property.name == "vertical_ratio": + if !(mode & PROPORTION_MODE.HEIGHT_PROPORTION): + property.usage |= PROPERTY_USAGE_READ_ONLY +func _get_minimum_size() -> Vector2: + return _min_size + + + +func _sort_children() -> void: + if _ignore_resize: return + _ignore_resize = true + + if mode != PROPORTION_MODE.NONE: + # Sets the min size according to it's dimentions and proportion mode + + var ancher_size : Vector2 = get_parent_area_size() + if ancher: ancher_size = ancher.size + var child_min_size := _get_children_min_size() + var _old_min_size := _min_size + + if mode & PROPORTION_MODE.WIDTH != 0: + _min_size.x = ancher_size.x * horizontal_ratio + else: + _min_size.x = child_min_size.x + if mode & PROPORTION_MODE.HEIGHT != 0: + _min_size.y = ancher_size.y * vertical_ratio + else: + _min_size.y = child_min_size.y + + if _min_size != _old_min_size: + update_minimum_size() + elif _min_size != Vector2.ZERO: + # Sets min size to default + + _min_size = Vector2.ZERO + update_minimum_size() + + _ignore_resize = false + _fit_children() + + + +func _fit_children() -> void: + for child : Control in _get_control_children(): + _fit_child(child) +func _fit_child(child : Control) -> void: + var child_size := child.get_minimum_size() + var ancher_size : Vector2 = ancher.size if ancher else get_parent_area_size() + var set_pos : Vector2 + + # Gets the ancher_size according to this node's dimentions and proportion mode + if mode & PROPORTION_MODE.WIDTH_PROPORTION > 0: + ancher_size.x = ancher_size.x * horizontal_ratio + + # Expands or repositions child, according to ancher and size flages + match child.size_flags_horizontal & ~SIZE_EXPAND: + SIZE_FILL: + child_size.x = ancher_size.x + set_pos.x = 0 + SIZE_SHRINK_BEGIN: + child_size.x = max(child_size.x, ancher_size.x) + set_pos.x = 0 + SIZE_SHRINK_CENTER: + child_size.x = max(child_size.x, ancher_size.x) + set_pos.x = max((ancher_size.x - child_size.x) * 0.5, 0) + SIZE_SHRINK_END: + child_size.x = max(child_size.x, ancher_size.x) + set_pos.x = max(ancher_size.x - child_size.x, 0) + if mode == PROPORTION_MODE.WIDTH: + child_size.y = size.y + + # Gets the ancher_size according to this node's dimentions and proportion mode + if mode & PROPORTION_MODE.HEIGHT_PROPORTION > 0: + ancher_size.y = ancher_size.y * vertical_ratio + + # Expands or repositions child, according to ancher and size flages + match child.size_flags_vertical & ~SIZE_EXPAND: + SIZE_FILL: + child_size.y = ancher_size.y + set_pos.y = 0 + SIZE_SHRINK_BEGIN: + child_size.y = max(child_size.y, ancher_size.y) + set_pos.y = 0 + SIZE_SHRINK_CENTER: + child_size.y = max(child_size.y, ancher_size.y) + set_pos.y = max((size.y - child_size.y) * 0.5, 0) + SIZE_SHRINK_END: + child_size.y = max(child_size.y, ancher_size.y) + set_pos.y = max(size.y - child_size.y, 0) + if mode == PROPORTION_MODE.HEIGHT: + child_size.x = size.x + + fit_child_in_rect(child, Rect2(set_pos, child_size)) +func _get_children_min_size() -> Vector2: + var ret := Vector2.ZERO + for child : Control in _get_control_children(): + ret = ret.max(child.get_combined_minimum_size()) + return ret +func _get_control_children() -> Array[Control]: + var ret : Array[Control] + ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible)) + return ret + + + +func _get_allowed_size_flags_horizontal() -> PackedInt32Array: + return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END] +func _get_allowed_size_flags_vertical() -> PackedInt32Array: + return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END] + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/Routers/Base/Page.gd b/godot/addons/FreeControl/src/CustomClasses/Routers/Base/Page.gd new file mode 100644 index 0000000..474ec89 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Routers/Base/Page.gd @@ -0,0 +1,77 @@ +@tool +class_name Page extends Container +## A standardized [Container] node for Routers to use, such as [RouterStack]. + + +## Emits when an event is requested to the attached Router parent. +## [br][br] +## If this Router is a decedent of another [Page], connect that [Page]'s +## [method emit_event] with this [Signal]. +signal event_action(event_name : String, args : Variant) + +@warning_ignore("unused_signal") +## Emits when this page is added as a child and finished animation by a Router. +signal entered +@warning_ignore("unused_signal") +## Emits when this page is added as a child. +signal entering +@warning_ignore("unused_signal") +## Emits when this page is about to be removed as a child and finished animation by a Router. +signal exited +@warning_ignore("unused_signal") +## Emits when this page is marked to be removed as a child. +signal exiting + + + +## Requests an event to the attached Router parent. +func emit_event(event_name : String, args : Variant) -> void: + event_action.emit(event_name, args) + + + +func _enter_tree() -> void: + if !Engine.is_editor_hint(): + clip_contents = true +func _init() -> void: + sort_children.connect(_sort_children) + + + +func _sort_children() -> void: + for child : Node in get_children(): + if child is Control: _update_child(child) +func _update_child(child : Control): + var child_min_size := child.get_minimum_size() + var result_size := child_min_size + + var set_pos : Vector2 + match child.size_flags_horizontal & ~SIZE_EXPAND: + SIZE_FILL: + result_size.x = max(result_size.x, size.x) + set_pos.x = (size.x - result_size.x) * 0.5 + SIZE_SHRINK_BEGIN: + set_pos.x = 0 + SIZE_SHRINK_CENTER: + set_pos.x = (size.x - result_size.x) * 0.5 + SIZE_SHRINK_END: + set_pos.x = size.x - result_size.x + match child.size_flags_vertical & ~SIZE_EXPAND: + SIZE_FILL: + result_size.y = max(result_size.y, size.y) + set_pos.y = (size.y - result_size.y) * 0.5 + SIZE_SHRINK_BEGIN: + set_pos.y = 0 + SIZE_SHRINK_CENTER: + set_pos.y = (size.y - result_size.y) * 0.5 + SIZE_SHRINK_END: + set_pos.y = size.y - result_size.y + + fit_child_in_rect(child, Rect2(set_pos, result_size)) + + + +func _get_allowed_size_flags_horizontal() -> PackedInt32Array: + return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END] +func _get_allowed_size_flags_vertical() -> PackedInt32Array: + return [SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END] diff --git a/godot/addons/FreeControl/src/CustomClasses/Routers/Base/PageInfo.gd b/godot/addons/FreeControl/src/CustomClasses/Routers/Base/PageInfo.gd new file mode 100644 index 0000000..c6db831 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Routers/Base/PageInfo.gd @@ -0,0 +1,43 @@ +@tool +class_name PageInfo extends Resource +## A [Resource] for keeping stack of [Page] information for a Router, such as [RouterStack]. + + +var _page : Control +var _auto_clean : bool +var _enter_animate : SwapContainer.ANIMATION_TYPE +var _exit_animate : SwapContainer.ANIMATION_TYPE + + +## Static create function for this [Resource]. +static func create( + page: Page, + enter_animate : SwapContainer.ANIMATION_TYPE, + exit_animate : SwapContainer.ANIMATION_TYPE, + auto_clean : bool +) -> PageInfo: + var info = PageInfo.new() + info._page = page + info._enter_animate = enter_animate + info._exit_animate = exit_animate + info._auto_clean = auto_clean + + return info + +## Gets the current [Page] held by this [Resource]. +func get_page() -> Page: return _page +## Gets the saved enter animation. +func get_enter_animation() -> SwapContainer.ANIMATION_TYPE: return _enter_animate +## Gets the saved exit animation. +func get_exit_animation() -> SwapContainer.ANIMATION_TYPE: return _exit_animate + + +func _notification(what): + if ( + what == NOTIFICATION_PREDELETE && + _auto_clean && + _page && + is_instance_valid(_page) + ): + _page.queue_free() + _page = null diff --git a/godot/addons/FreeControl/src/CustomClasses/Routers/RouterStack.gd b/godot/addons/FreeControl/src/CustomClasses/Routers/RouterStack.gd new file mode 100644 index 0000000..4509ce6 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/Routers/RouterStack.gd @@ -0,0 +1,427 @@ +@tool +class_name RouterStack extends PanelContainer +## Handles a [Control] stack, between [Page] nodes, using [SwapContainer]. + +## The Animation type to transition with. +const ANIMATION_TYPE = SwapContainer.ANIMATION_TYPE + +## Emits when the current [Page] requests an event. +signal event_action(event : String, args : Variant) + +## Emits at the start of a transition. +signal start_animation +## Emits at the end of a transition. +signal end_animation + +## The filepath to the [Page] node this to load on ready. If this path is invaild, +## or not to a [PackedScene] with a [Page] node root, nothing will be loaded on ready. +@export_file("*.tscn") var starting_page : String: + set(val): + if val != starting_page: + starting_page = val + if Engine.is_editor_hint() && is_node_ready(): + _clear_all_pages() + if ResourceLoader.exists(starting_page) && starting_page.get_extension() == "tscn": + route(starting_page, ANIMATION_TYPE.NONE, ANIMATION_TYPE.NONE) +## The max size of the stack. If the stack is too big, it will clear the oldest on +## the stack first. +@export_range(0, 1000, 1, "or_greater") var max_stack : int = 50: + set(val): + val = max(val, 1) + if max_stack != val: + max_stack = val + + +@export_group("Animation") +## Starts animation with the [Control] node outside of the visisble screen. +@export var from_outside_screen : bool: + set(val): + if val != from_outside_screen: + from_outside_screen = val + _stack.from_outside_screen = val +## Starts animation an offset of this amount of pixels (away from the center), start +## at the position the [Control] originally would be placed at. +@export var offset : float: + set(val): + if val != offset: + offset = val + _stack.offset = val + +@export_group("Easing") +## The [enum Tween.EaseType] that will be used as the new [Control] transitions in. +@export var ease_enter : Tween.EaseType = Tween.EaseType.EASE_IN_OUT: + set(val): + if val != ease_enter: + ease_enter = val + _stack.ease_enter = val +## The [enum Tween.EaseType] that will be used as the current [Control] transitions out. +@export var ease_exit : Tween.EaseType = Tween.EaseType.EASE_IN_OUT: + set(val): + if val != ease_exit: + ease_exit = val + _stack.ease_exit = val + +@export_group("Transition") +## The [enum Tween.TransitionType] that will be used as the new [Control] transitions in. +@export var transition_enter : Tween.TransitionType = Tween.TransitionType.TRANS_CUBIC: + set(val): + if val != transition_enter: + transition_enter = val + _stack.transition_enter = val +## The [enum Tween.TransitionType] that will be used as the current [Control] transitions out. +@export var transition_exit : Tween.TransitionType = Tween.TransitionType.TRANS_CUBIC: + set(val): + if val != transition_exit: + transition_exit = val + _stack.transition_exit = val + +@export_group("Duration") +## The duration of the animation used as the new [Control] transitions in. +@export var duration_enter : float = 0.35: + set(val): + if val != duration_enter: + duration_enter = val + _stack.duration_enter = val +## The duration of the animation used as the current [Control] transitions out. +@export var duration_exit : float = 0.35: + set(val): + if val != duration_exit: + duration_exit = val + _stack.duration_exit = val + + +var _page_stack : Array[PageInfo] = [] +var _params : Dictionary = {} +var _stack : SwapContainer + + + +## Emits the [Signal Page.entered] signal on the current [Page] displayed. +## [br][br] +## If this Router is a decedent of another [Page], connect that [Page]'s +## [Signal Page.entered] with this method. +func emit_entered() -> void: + var curr_page : Page = null if _page_stack.is_empty() else _page_stack[0].get_page() + if curr_page: + curr_page.entered.emit() +## Emits the [Signal Page.entering] signal on the current [Page] displayed. +## [br][br] +## If this Router is a decedent of another [Page], connect that [Page]'s +## [Signal Page.entering] with this method. +func emit_entering() -> void: + var curr_page : Page = null if _page_stack.is_empty() else _page_stack[0].get_page() + if curr_page: + curr_page.entering.emit() +## Emits the [Signal Page.exited] signal on the current [Page] displayed. +## [br][br] +## If this Router is a decedent of another [Page], connect that [Page]'s +## [Signal Page.exited] with this method. +func emit_exited() -> void: + var curr_page : Page = null if _page_stack.is_empty() else _page_stack[0].get_page() + if curr_page: + curr_page.exited.emit() +## Emits the [Signal Page.exiting] signal on the current [Page] displayed. +## [br][br] +## If this Router is a decedent of another [Page], connect that [Page]'s +## [Signal Page.exiting] with this method. +func emit_exiting() -> void: + var curr_page : Page = null if _page_stack.is_empty() else _page_stack[0].get_page() + if curr_page: + curr_page.exiting.emit() + + +## Routes to a [Page] node given by a file path to a [PackedScene]. +func route( + page_path : String, + enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + params : Dictionary = {}, + args : Dictionary = {} +) -> Page: + var packed : PackedScene = await _ResourceLoader.new(get_tree().process_frame, page_path).finished + if packed == null: + push_error("An error occured while attempting to load file at filepath '", page_path, "'") + return null + + return await route_packed( + packed, + enter_animation, + exit_animation, + params, + args + ) +## Routes to a [Page] node given by a [PackedScene]. +func route_packed( + page_scene : PackedScene, + enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + params : Dictionary = {}, + args : Dictionary = {} +) -> Page: + if page_scene == null: + push_error("page_scene cannot be 'null'") + return null + + return await route_node( + page_scene.instantiate(), + enter_animation, + exit_animation, + params, + args + ) +## Routes to a given [Page] node. +func route_node( + page : Page, + enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + params : Dictionary = {}, + args : Dictionary = {} +) -> Page: + if !page: + push_error("page cannot be 'null'") + return null + _params = params + + var enter_page : PageInfo = PageInfo.create( + page, + enter_animation, + exit_animation, + args.get("auto_clean", true) + ) + + _stack.set_modifers(args) + _append_to_page_queue(enter_page) + + await _handle_swap( + enter_page.get_page(), + enter_animation, + exit_animation + ) + + return page + + +## Routes to a [Page] node given by a file path to a [PackedScene]. Clears stack. +func navigate( + page_path : String, + enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + params : Dictionary = {}, + args : Dictionary = {} +) -> Page: + var packed : PackedScene = await _ResourceLoader.new(get_tree().process_frame, page_path).finished + if packed == null: + push_error("An error occured while attempting to load file at filepath '", page_path, "'") + return null + + return await navigate_packed( + packed, + enter_animation, + exit_animation, + params, + args + ) +## Routes to a [Page] node given by a [PackedScene]. Clears stack. +func navigate_packed( + page_scene : PackedScene, + enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + params : Dictionary = {}, + args : Dictionary = {} +) -> Page: + if page_scene == null: + push_error("page_scene cannot be 'null'") + return null + + return await navigate_node( + page_scene.instantiate(), + enter_animation, + exit_animation, + params, + args + ) +## Routes to a given [Page] node. Clears stack. +func navigate_node( + page : Page, + enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + params : Dictionary = {}, + args : Dictionary = {} +) -> Page: + if !page: + push_error("page cannot be 'null'") + return null + _params = params + + var enter_info : PageInfo = PageInfo.create( + page, + enter_animation, + exit_animation, + args.get("auto_clean", true) + ) + + _stack.set_modifers(args) + _append_to_page_queue(enter_info) + + await _handle_swap( + enter_info.get_page(), + enter_animation, + exit_animation + ) + _clear_stack() + + return page + + +## Routes to the previous [Control] on the stack. If the stack is empty, nothing will happen. +func back( + enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + params : Dictionary = {}, + args : Dictionary = {} +) -> void: + if is_empty(): return + _params = params + _stack.set_modifers(args) + + var exit_page : PageInfo = _page_stack.pop_back() + var enter_page : PageInfo = _page_stack.back() + + enter_page.get_page().event_action.connect(event_action.emit) + + if enter_animation == ANIMATION_TYPE.DEFAULT: + enter_animation = _reverse_animate(exit_page.get_exit_animation()) + if exit_animation == ANIMATION_TYPE.DEFAULT: + exit_animation = _reverse_animate(exit_page.get_enter_animation()) + + await _handle_swap( + enter_page.get_page(), + enter_animation, + exit_animation, + false + ) + + +## Gets the parameters of the last route. +func get_params() -> Dictionary: + return _params +## Gets the current stack size. +func stack_size() -> int: + return _page_stack.size() +## Returns if the stack is empty. +func is_empty() -> bool: + return _page_stack.size() <= 1 +## Returns the current [Page] on display. +func get_current_page() -> PageInfo: + return null if _page_stack.is_empty() else _page_stack.back() + + +func _handle_swap( + enter_page : Page, + enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + front : bool = true +) -> void: + var exit_page : Page = _stack.get_current() + + if enter_page: + enter_page.entering.emit() + if exit_page: + exit_page.exiting.emit() + + await _stack.swap_control( + enter_page, + enter_animation, + exit_animation, + front + ) + + if enter_page: + enter_page.entered.emit() + if exit_page: + exit_page.exited.emit() + + + +func _append_to_page_queue(page_node: PageInfo) -> void: + if !_page_stack.is_empty(): + _page_stack.back().get_page().event_action.disconnect(event_action.emit) + if _page_stack.size() > max_stack: + _page_stack.pop_front() + _page_stack.append(page_node) + + var page := page_node.get_page() + if !page.event_action.is_connected(event_action.emit): + page.event_action.connect(event_action.emit) + +func _reverse_animate(animation : ANIMATION_TYPE) -> ANIMATION_TYPE: + match animation: + ANIMATION_TYPE.NONE: + return ANIMATION_TYPE.NONE + ANIMATION_TYPE.LEFT: + return ANIMATION_TYPE.RIGHT + ANIMATION_TYPE.RIGHT: + return ANIMATION_TYPE.LEFT + ANIMATION_TYPE.TOP: + return ANIMATION_TYPE.BOTTOM + ANIMATION_TYPE.BOTTOM: + return ANIMATION_TYPE.TOP + return ANIMATION_TYPE.NONE + + +func _clear_stack() -> void: + _page_stack = [_page_stack.back()] +func _clear_all_pages() -> void: + _page_stack = [] + + + +func _init() -> void: + _stack = SwapContainer.new() + add_child(_stack) + _stack.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + + _stack.start_animation.connect(start_animation.emit) + _stack.end_animation.connect(end_animation.emit) + + _stack.from_outside_screen = from_outside_screen + _stack.offset = offset + + _stack.ease_enter = ease_enter + _stack.ease_exit = ease_exit + + _stack.transition_enter = transition_enter + _stack.transition_exit = transition_exit + + _stack.duration_enter = duration_enter + _stack.duration_exit = duration_exit + + + +class _ResourceLoader: + signal finished(scene : PackedScene) + + var _resource_name : String + + func _init(check_signal : Signal, path : StringName) -> void: + _resource_name = path + + if !ResourceLoader.exists(_resource_name): + push_error("Error - Invaild Resource Loaded") + check_signal.connect(_delay_failsave, CONNECT_ONE_SHOT) + return + + check_signal.connect(_on_signal) + ResourceLoader.load_threaded_request( + _resource_name, + "PackedScene" + ) + + func _on_signal() -> void: + match ResourceLoader.load_threaded_get_status(_resource_name): + ResourceLoader.THREAD_LOAD_INVALID_RESOURCE, ResourceLoader.THREAD_LOAD_FAILED: + finished.emit(null) + ResourceLoader.THREAD_LOAD_LOADED: + finished.emit(ResourceLoader.load_threaded_get(_resource_name)) + func _delay_failsave() -> void: + finished.emit(null) diff --git a/godot/addons/FreeControl/src/CustomClasses/SizeController/MaxRatioContainer.gd b/godot/addons/FreeControl/src/CustomClasses/SizeController/MaxRatioContainer.gd new file mode 100644 index 0000000..a682f83 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/SizeController/MaxRatioContainer.gd @@ -0,0 +1,77 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name MaxRatioContainer extends MaxSizeContainer +## A container that limits an axis of it's size, to a maximum value, relative +## to the value of it's other axis. + +## The behavior this node will exhibit based on an axis. +enum MAX_RATIO_MODE { + NONE, ## No maximum value for either axis on this container. + WIDTH, ## Sets and expands children height to be proportionate of width. + WIDTH_PROPORTION, ## Sets the maximum height value of this container to be proportionate of width. + HEIGHT, ## Sets and expands children width to be proportionate of height. + HEIGHT_PROPORTION ## Sets the maximum width value of this container to be proportionate of height. +} + +## The ratio mode used to expand and limit children. +@export var mode : MAX_RATIO_MODE = MAX_RATIO_MODE.NONE: + set(val): + if val != mode: + mode = val + queue_sort() +## The ratio value used to expand and limit children. +@export_range(0.001, 10, 0.001, "or_greater") var ratio : float = 1.0: + set(val): + if val != ratio: + ratio = val + queue_sort() + +func _validate_property(property: Dictionary) -> void: + if property.name == "max_size": + property.usage |= PROPERTY_USAGE_READ_ONLY +func _get_minimum_size() -> Vector2: + var parent := get_parent_area_size() + var min_size := super() + + var current_size := min_size.max(size) + match mode: + MAX_RATIO_MODE.NONE: + current_size = Vector2(0, 0) + MAX_RATIO_MODE.WIDTH, MAX_RATIO_MODE.WIDTH_PROPORTION: + current_size = Vector2(0, minf(current_size.x * ratio, parent.y)) + MAX_RATIO_MODE.HEIGHT, MAX_RATIO_MODE.HEIGHT_PROPORTION: + current_size = Vector2(minf(current_size.y * ratio, parent.x), 0) + + min_size = min_size.max(current_size) + return min_size + + + +## Updates the _max_size according to the ratio mode and current dimentions +func _update_children() -> void: + var parent := get_parent_area_size() + var min_size := get_combined_minimum_size() + + # Adjusts max_size itself accouring to the ratio mode and current dimentions + match mode: + MAX_RATIO_MODE.NONE: + _max_size = Vector2(-1, -1) + MAX_RATIO_MODE.WIDTH: + _max_size = Vector2(-1, minf(size.x * ratio, parent.y)) + MAX_RATIO_MODE.WIDTH_PROPORTION: + _max_size = Vector2(-1, min(size.x * ratio, parent.y, min_size.y)) + MAX_RATIO_MODE.HEIGHT: + _max_size = Vector2(minf(size.y * ratio, parent.x), -1) + MAX_RATIO_MODE.HEIGHT_PROPORTION: + _max_size = Vector2(min(size.y * ratio, parent.x, min_size.x), -1) + + var new_size := size + if _max_size.x >= 0: + new_size.x = _max_size.x + if _max_size.y >= 0: + new_size.y = _max_size.y + set_deferred("size", new_size) + + super() + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/SizeController/MaxSizeContainer.gd b/godot/addons/FreeControl/src/CustomClasses/SizeController/MaxSizeContainer.gd new file mode 100644 index 0000000..eaeba1d --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/SizeController/MaxSizeContainer.gd @@ -0,0 +1,78 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name MaxSizeContainer extends Container +## A container that limits it's size to a maximum value. + +var _max_size := -Vector2.ONE +## The maximum size this container can possess. +## [br][br] +## If one of the axis is [code]-1[/code], then it is boundless. +@export var max_size : Vector2 = -Vector2.ONE: + get: return _max_size + set(val): + _max_size = val + queue_sort() + +func _init() -> void: + sort_children.connect(_handle_sort, CONNECT_DEFERRED) +func _set(property: StringName, value: Variant) -> bool: + if property == "size": + return true + return false +func _get_minimum_size() -> Vector2: + var min_size : Vector2 = Vector2.ZERO + for child : Control in _get_control_children(): + min_size = min_size.max(child.get_combined_minimum_size()) + return min_size +func _get_control_children() -> Array[Control]: + var ret : Array[Control] + ret.assign(get_children().filter(func(child : Node): return child is Control && child.visible)) + return ret + + + +## A helper function that should be called whenever this node's size needs to be changed, or when it's children are changed. +func _handle_sort() -> void: + update_minimum_size() + _update_children() + +func _update_children() -> void: + for child : Control in _get_control_children(): + _update_child(child) +func _update_child(child : Control): + var child_min_size := child.get_minimum_size() + var set_pos : Vector2 + var result_size := Vector2( + size.x if _max_size.x < 0 else minf(size.x, _max_size.x), + size.y if _max_size.y < 0 else minf(size.y, _max_size.y) + ) + + if child.size_flags_horizontal & SIZE_EXPAND_FILL: + result_size.x = maxf(result_size.x, child_min_size.x) + else: + result_size.x = minf(result_size.x, child_min_size.x) + if child.size_flags_vertical & SIZE_EXPAND_FILL: + result_size.y = maxf(result_size.y, child_min_size.y) + else: + result_size.y = minf(result_size.y, child_min_size.y) + + + match child.size_flags_horizontal & ~SIZE_EXPAND: + SIZE_SHRINK_CENTER, SIZE_FILL: + set_pos.x = (size.x - result_size.x) * 0.5 + SIZE_SHRINK_BEGIN: + set_pos.x = 0 + SIZE_SHRINK_END: + set_pos.x = size.x - result_size.x + match child.size_flags_vertical & ~SIZE_EXPAND: + SIZE_SHRINK_CENTER, SIZE_FILL: + set_pos.y = (size.y - result_size.y) * 0.5 + SIZE_SHRINK_BEGIN: + set_pos.y = 0 + SIZE_SHRINK_END: + set_pos.y = size.y - result_size.y + + child.position = set_pos + child.size = result_size + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/SwapContainer/SwapContainer.gd b/godot/addons/FreeControl/src/CustomClasses/SwapContainer/SwapContainer.gd new file mode 100644 index 0000000..ab94e29 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/SwapContainer/SwapContainer.gd @@ -0,0 +1,297 @@ +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. +@tool +class_name SwapContainer extends Container +## A [Container] node that provides transitions between different [Control] nodes. + +## The Animation type to transition with. +enum ANIMATION_TYPE { + DEFAULT, ## The same as [constant LEFT]. + NONE, ## No Transition + LEFT, ## Either moves towards or away the left + RIGHT, ## Either moves towards or away the right + TOP, ## Either moves towards or away the top + BOTTOM ## Either moves towards or away the bottom +} + +## Emits at the start of a transition. +signal start_animation +## Emits at the end of a transition. +signal end_animation + + +var _enter_tween : Tween = null +var _exit_tween : Tween = null +var _current_node : Control + + +@export_group("Animation") +## Starts animation with the [Control] node outside of the visisble screen. +@export var from_outside_screen : bool +## Starts animation an offset of this amount of pixels (away from the center), start +## at the position the [Control] originally would be placed at. +@export var offset : float + +@export_group("Easing") +## The [enum Tween.EaseType] that will be used as the new [Control] transitions in. +@export var ease_enter : Tween.EaseType = Tween.EaseType.EASE_IN_OUT +## The [enum Tween.EaseType] that will be used as the current [Control] transitions out. +@export var ease_exit : Tween.EaseType = Tween.EaseType.EASE_IN_OUT + +@export_group("Transition") +## The [enum Tween.TransitionType] that will be used as the new [Control] transitions in. +@export var transition_enter : Tween.TransitionType = Tween.TransitionType.TRANS_CUBIC +## The [enum Tween.TransitionType] that will be used as the current [Control] transitions out. +@export var transition_exit : Tween.TransitionType = Tween.TransitionType.TRANS_CUBIC + +@export_group("Duration") +## The duration of the animation used as the new [Control] transitions in. +@export var duration_enter : float = 0.35 +## The duration of the animation used as the current [Control] transitions out. +@export var duration_exit : float = 0.35 + + +## Causes the current [Control] node to transition out and [param node] +## to transition in. +func swap_control( + node : Control, + enter_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + exit_animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, + front : bool = true +) -> Control: + var _old_node := _current_node + _parent_control(node, front) + + start_animation.emit() + _current_node = node + await _perform_animations( + node, + _old_node, + enter_animation, + exit_animation + ) + + if _old_node: _unparent_control(_old_node) + end_animation.emit() + + return _old_node +## Sets all export members with a simple [Dictionary]. +func set_modifers(args : Dictionary) -> void: + if args.has("ease_enter"): + ease_enter = args.get("ease_enter") + if args.has("ease_exit"): + ease_exit = args.get("ease_exit") + + if args.has("transition_enter"): + transition_enter = args.get("transition_enter") + if args.has("transition_exit"): + transition_exit = args.get("transition_exit") + + if args.has("duration_enter"): + duration_enter = args.get("duration_enter") + if args.has("duration_exit"): + duration_exit = args.get("duration_exit") + + +## Gets the current [Control] displayed. [code]null[/code] if there is currently no such +## [Control]. +func get_current() -> Control: + return _current_node + + + +func _parent_control(node: Control, front : bool) -> void: + if !node: return + if !node.get_parent(): + add_child(node) + + if is_inside_tree(): + node.hide() + get_tree().process_frame.connect(node.show, CONNECT_ONE_SHOT) + + if front: + node.move_to_front() + else: + move_child(node, 0) + + node.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) +func _unparent_control(node: Control) -> void: + remove_child(node) + + + +func _perform_animations( + enter_node: Control, + exit_node: Control, + enter_animation: ANIMATION_TYPE, + exit_animation: ANIMATION_TYPE +) -> void: + if enter_animation == ANIMATION_TYPE.NONE && exit_animation == ANIMATION_TYPE.NONE: + if enter_node: + enter_node.position = Vector2.ZERO + return + _tween_settup() + + if enter_node: + _handle_enter_animation( + enter_node, + _enter_tween, + enter_animation + ) + else: + _enter_tween.finished.emit() + _enter_tween.kill() + _enter_tween = null + if exit_node: + _handle_exit_animation( + exit_node, + _exit_tween, + exit_animation, + ) + else: + _exit_tween.finished.emit() + _exit_tween.kill() + _exit_tween = null + + await _await_animations() +func _handle_enter_animation( + node: Control, + animation_tween : Tween, + animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, +) -> void: + var border := _get_border() + + match animation: + ANIMATION_TYPE.DEFAULT, ANIMATION_TYPE.LEFT: + animation_tween.tween_method( + func (val): node.position.x = val, + border.position.x, + 0, + duration_enter + ) + ANIMATION_TYPE.RIGHT: + animation_tween.tween_method( + func (val): node.position.x = val, + border.size.x - border.position.x, + 0, + duration_enter + ) + ANIMATION_TYPE.TOP: + animation_tween.tween_method( + func (val): node.position.y = val, + border.position.y, + 0, + duration_enter + ) + ANIMATION_TYPE.BOTTOM: + animation_tween.tween_method( + func (val): node.position.y = val, + border.size.y - border.position.y, + 0, + duration_enter + ) + _: + node.position = Vector2.ZERO + return + + animation_tween.play() +func _handle_exit_animation( + node: Control, + animation_tween : Tween, + animation: ANIMATION_TYPE = ANIMATION_TYPE.DEFAULT, +) -> void: + var border : Rect2 = _get_border() + + match animation: + ANIMATION_TYPE.DEFAULT, ANIMATION_TYPE.LEFT: + animation_tween.tween_method( + func (val): node.position.x = val, + 0, + border.size.x - border.position.x, + duration_enter + ) + ANIMATION_TYPE.RIGHT: + animation_tween.tween_method( + func (val): node.position.x = val, + 0, + border.position.x, + duration_enter + ) + ANIMATION_TYPE.TOP: + animation_tween.tween_method( + func (val): node.position.y = val, + 0, + border.size.y - border.position.y, + duration_enter + ) + ANIMATION_TYPE.BOTTOM: + animation_tween.tween_method( + func (val): node.position.y = val, + 0, + border.position.y, + duration_enter + ) + _: + node.position = Vector2.ZERO + return + + animation_tween.play() + + +func _tween_settup() -> void: + if _enter_tween && _enter_tween.is_running(): + _enter_tween.finished.emit() + _enter_tween.kill() + _enter_tween = create_tween() + + _enter_tween.set_ease(ease_enter) + _enter_tween.set_trans(transition_enter) + _enter_tween.stop() + + if _exit_tween && _exit_tween.is_running(): + _exit_tween.finished.emit() + _exit_tween.kill() + _exit_tween = create_tween() + + _exit_tween.set_ease(ease_exit) + _exit_tween.set_trans(transition_exit) + _exit_tween.stop() + +func _get_border() -> Rect2: + var boarder : Rect2 + + if from_outside_screen: + boarder.position = -global_position - size + boarder.size = get_viewport_rect().size - size + else: + boarder.position = -size + boarder.size = Vector2.ZERO + + boarder.position -= Vector2(offset, offset) + return boarder + + +func _await_animations() -> void: + @warning_ignore("incompatible_ternary") + await _SignalMerge.new( + _enter_tween.finished if _enter_tween && _enter_tween.is_running() else null, + _exit_tween.finished if _exit_tween && _exit_tween.is_running() else null + ) .finished + + +class _SignalMerge: + signal finished + var _activate : bool = false + + func _init(enter, exit) -> void: + _register(enter) + _register(exit) + func _register(arg) -> void: + if arg is Signal: + arg.connect(_unleash) + return + _unleash() + func _unleash() -> void: + if _activate: finished.emit() + _activate = true + +# Made by Xavier Alvarez. A part of the "FreeControl" Godot addon. diff --git a/godot/addons/FreeControl/src/CustomClasses/TransitionContainers/ModulateTransitionContainer.gd b/godot/addons/FreeControl/src/CustomClasses/TransitionContainers/ModulateTransitionContainer.gd new file mode 100644 index 0000000..f84536a --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/TransitionContainers/ModulateTransitionContainer.gd @@ -0,0 +1,109 @@ +@tool +class_name ModulateTransitionContainer extends Container +## A [Control] node with changable that allows easy [member CanvasItem.modulate] animation between colors. + + + +@export_group("Alpha Override") +## The colors to animate between. +@export var colors : PackedColorArray = [Color.WHITE, Color(1.0, 1.0, 1.0, 0.5)]: + set(val): + if colors != val: + colors = val + focused_color = focused_color + force_color(_focused_color) +var _focused_color : int = 0 +## The index of currently used color from [member colors]. +## This member is [code]-1[/code] if [member colors] is empty. +@export var focused_color : int: + get: return _focused_color + set(val): + if colors.size() == 0: + _focused_color = -1 + return + + val = clampi(val, 0, colors.size() - 1) + if _focused_color != val: + _focused_color = val + _on_set_color() +## If [code]true[/code] this node will only animate over [member CanvasItem.self_modulate]. Otherwise, +## it will animate over [member CanvasItem.modulate]. +@export var modulate_self : bool = false + +@export_group("Tween Override") +## The duration of color animations. +@export var transitionTime : float = 0.2 +## The [Tween.EaseType] of color animations. +@export var easeType : Tween.EaseType = Tween.EaseType.EASE_OUT_IN +## The [Tween.TransitionType] of color animations. +@export var transition : Tween.TransitionType = Tween.TransitionType.TRANS_CIRC +## If [code]true[/code] animations can be interupted midway. Otherwise, any change in the [param focused_color] +## will be queued to be reflected after any currently running animation. +@export var can_cancle : bool = true + + +var _color_tween : Tween = null +var _current_focused_color : int + + +## Sets the current color index. +## [br][br] +## Also see: [member focused_color]. +func set_color(color: int) -> void: + focused_color = color +## Sets the current color index. Performing this will ignore any animation and instantly set the color. +## [br][br] +## Also see: [member focused_color]. +func force_color(color: int) -> void: + if _color_tween && _color_tween.is_running(): + if !can_cancle: return + _color_tween.kill() + _current_focused_color = color + modulate = colors[color] + +## Gets the current color attributed to the current color index. +func get_current_color() -> Color: + if _focused_color == -1: return 1 + return colors[_focused_color] + + + +func _on_set_color(): + if _focused_color == _current_focused_color: + return + if can_cancle: + if _color_tween: _color_tween.kill() + elif _color_tween && _color_tween.is_running(): + return + _current_focused_color = _focused_color + + _color_tween = create_tween() + _color_tween.tween_property( + self, + "self_modulate" if modulate_self else "modulate", + get_current_color(), + transitionTime + ) + _color_tween.finished.connect(_on_set_color, CONNECT_ONE_SHOT) + + + +func _init() -> void: + _current_focused_color = _focused_color + sort_children.connect(_handle_children) + +func _property_can_revert(property: StringName) -> bool: + if property == "colors": + return colors.size() == 2 && colors[0] == Color.WHITE && colors[1] == Color(1.0, 1.0, 1.0, 0.5) + return false + + +func _handle_children() -> void: + for child in get_children(): + fit_child_in_rect(child, Rect2(Vector2.ZERO, size)) +func _get_minimum_size() -> Vector2: + var min_size : Vector2 + for child : Node in get_children(): + if child is Control: + min_size = min_size.max(child.get_combined_minimum_size()) + return min_size diff --git a/godot/addons/FreeControl/src/CustomClasses/TransitionContainers/StyleTransitionContainer.gd b/godot/addons/FreeControl/src/CustomClasses/TransitionContainers/StyleTransitionContainer.gd new file mode 100644 index 0000000..aae5ee4 --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/TransitionContainers/StyleTransitionContainer.gd @@ -0,0 +1,126 @@ +@tool +class_name StyleTransitionContainer extends Container +## A [Container] node that add a [StyleTransitionPanel] node as the background. + + + +@export_group("Appearence Override") +## The stylebox used by [StyleTransitionPanel]. +@export var background : StyleBox: + set(val): + if _panel: + _panel.add_theme_stylebox_override("panel", val) + background = val + elif background != val: + background = val + +@export_group("Colors Override") +## The colors to animate between. +@export var colors : PackedColorArray = [ + Color.WEB_GRAY, + Color.DIM_GRAY +]: + set(val): + if _panel: + _panel.colors = val + colors = val + elif colors != val: + colors = val + +## The index of currently used color from [member colors]. +## This member is [code]-1[/code] if [member colors] is empty. +@export var focused_color : int: + set(val): + if _panel: + _panel.focused_color = val + focused_color = val + elif focused_color != val: + focused_color = val + +@export_group("Tween Override") +## The duration of color animations. +@export var transitionTime : float = 0.2: + set(val): + if _panel: + _panel.transitionTime = val + transitionTime = val + elif transitionTime != val: + transitionTime = val +## The [Tween.EaseType] of color animations. +@export var easeType : Tween.EaseType = Tween.EaseType.EASE_OUT_IN: + set(val): + if _panel: + _panel.easeType = val + easeType = val + elif easeType != val: + easeType = val +## The [Tween.TransitionType] of color animations. +@export var transition : Tween.TransitionType = Tween.TransitionType.TRANS_CIRC: + set(val): + if _panel: + _panel.transition = val + transition = val + elif transition != val: + transition = val +## If [code]true[/code] animations can be interupted midway. Otherwise, any change in the [param focused_color] +## will be queued to be reflected after any currently running animation. +@export var can_cancle : bool = true: + set(val): + if _panel: + _panel.can_cancle = val + can_cancle = val + elif can_cancle != val: + can_cancle = val + + +var _panel : StyleTransitionPanel + + +## Sets the current color index. +## [br][br] +## Also see: [member focused_color]. +func set_color(color: int) -> void: + if !_panel: return + _panel.set_color(color) +## Sets the current color index. Performing this will ignore any animation and instantly set the color. +## [br][br] +## Also see: [member focused_color]. +func force_color(color: int) -> void: + if !_panel: return + _panel.force_color(color) + +## Gets the current color attributed to the current color index. +func get_current_color() -> Color: + if !_panel: return Color.BLACK + return _panel.get_current_color() + + + +func _init() -> void: + _panel = StyleTransitionPanel.new() + _panel.mouse_filter = Control.MOUSE_FILTER_IGNORE + _panel.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + add_child(_panel) + move_child(_panel, 0) + + sort_children.connect(_handle_children) +func _ready() -> void: + if background: + _panel.add_theme_stylebox_override("panel", background) + return + background = _panel.get_theme_stylebox("panel") + +func _handle_children() -> void: + for child in get_children(): + fit_child_in_rect(child, Rect2(Vector2.ZERO, size)) +func _get_minimum_size() -> Vector2: + var min_size : Vector2 + for child : Node in get_children(): + if child is Control: + min_size = min_size.max(child.get_combined_minimum_size()) + return min_size + +func _property_can_revert(property: StringName) -> bool: + if property == "colors": + return colors.size() == 2 && colors[0] == Color.WEB_GRAY && colors[1] == Color.DIM_GRAY + return false diff --git a/godot/addons/FreeControl/src/CustomClasses/TransitionContainers/StyleTransitionPanel.gd b/godot/addons/FreeControl/src/CustomClasses/TransitionContainers/StyleTransitionPanel.gd new file mode 100644 index 0000000..c6f154b --- /dev/null +++ b/godot/addons/FreeControl/src/CustomClasses/TransitionContainers/StyleTransitionPanel.gd @@ -0,0 +1,106 @@ +@tool +class_name StyleTransitionPanel extends Panel +## A [Panel] node with changable that allows easy [member CanvasItem.self_modulate] animation between colors. + + + +@export_group("Colors Override") +## The colors to animate between. +@export var colors : PackedColorArray = [ + Color.WEB_GRAY, + Color.DIM_GRAY +]: + set(val): + if colors != val: + colors = val + focused_color = focused_color + force_color(_focused_color) +var _focused_color : int = 0 +## The index of currently used color from [member colors]. +## This member is [code]-1[/code] if [member colors] is empty. +@export var focused_color : int: + get: return _focused_color + set(val): + if colors.size() == 0: + _focused_color = -1 + return + + val = clampi(val, 0, colors.size() - 1) + if _focused_color != val: + _focused_color = val + _on_set_color() + +@export_group("Tween Override") +## The duration of color animations. +@export var transitionTime : float = 0.2 +## The [Tween.EaseType] of color animations. +@export var easeType : Tween.EaseType = Tween.EaseType.EASE_OUT_IN +## The [Tween.TransitionType] of color animations. +@export var transition : Tween.TransitionType = Tween.TransitionType.TRANS_CIRC +## If [code]true[/code] animations can be interupted midway. Otherwise, any change in the [param focused_color] +## will be queued to be reflected after any currently running animation. +@export var can_cancle : bool = true + + +var _color_tween : Tween = null +var _current_focused_color : int + + +## Sets the current color index. +## [br][br] +## Also see: [member focused_color]. +func set_color(color: int) -> void: + focused_color = color +## Sets the current color index. Performing this will ignore any animation and instantly set the color. +## [br][br] +## Also see: [member focused_color]. +func force_color(color: int) -> void: + if _color_tween && _color_tween.is_running(): + if !can_cancle: return + _color_tween.kill() + _focused_color = color + _safe_base_set_background() + self_modulate = get_current_color() + +## Gets the current color attributed to the current color index. +func get_current_color() -> Color: + if _focused_color == -1: return Color.BLACK + return colors[_focused_color] + + + +func _safe_base_set_background() -> void: + if has_theme_stylebox_override("panel"): return + + var background = StyleBoxFlat.new() + background.resource_local_to_scene = true + background.bg_color = Color.WHITE + add_theme_stylebox_override("panel", background) + +func _on_set_color(): + if _focused_color == _current_focused_color: + return + if can_cancle: + if _color_tween: _color_tween.kill() + elif _color_tween && _color_tween.is_running(): + return + _current_focused_color = _focused_color + + _safe_base_set_background() + _color_tween = create_tween() + _color_tween.tween_property( + self, + "self_modulate", + get_current_color(), + transitionTime + ) + _color_tween.finished.connect(_on_set_color, CONNECT_ONE_SHOT) + + +func _init() -> void: + _current_focused_color = _focused_color + _safe_base_set_background() +func _property_can_revert(property: StringName) -> bool: + if property == "colors": + return colors.size() == 2 && colors[0] == Color.WEB_GRAY && colors[1] == Color.DIM_GRAY + return false diff --git a/godot/addons/LICENSE b/godot/addons/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/godot/addons/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/godot/addons/utils/utils.gd b/godot/addons/utils/utils.gd index 2a846bc..325be72 100644 --- a/godot/addons/utils/utils.gd +++ b/godot/addons/utils/utils.gd @@ -1,5 +1,9 @@ class_name Utils +static func remove_children(node: Node): + for i in range(node.get_child_count()): + node.remove_child(node.get_child(i)) + static func get_class_name(value: Object) -> String: match value.get_script(): var script: @@ -26,3 +30,20 @@ static func to_str(value: Variant) -> String: var props: Dictionary = inst_to_dict(value) return "%s %s" % [name, to_str(props)] _: return str(value) + +static func propagate(node: Node, fn: StringName, args: Array, call_on_self: bool = true): + if call_on_self and node.has_method(fn): + node.callv(fn, args) + + for child in node.get_children(): + propagate(child, fn, args) + +static func propagate_input_event(node: Node, fn: StringName, event: InputEvent, call_on_self: bool = true): + if node.get_viewport().is_input_handled() or event.is_canceled(): + return + + if call_on_self and node.has_method(fn): + node.callv(fn, [event]) + + for child in node.get_children(): + propagate_input_event(child, fn, event) diff --git a/godot/project.godot b/godot/project.godot index 8aa8e78..edebd9c 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -21,7 +21,7 @@ PhantomCameraManager="*res://addons/phantom_camera/scripts/managers/phantom_came [editor_plugins] -enabled=PackedStringArray("res://addons/godot_object_serializer/plugin.cfg", "res://addons/phantom_camera/plugin.cfg") +enabled=PackedStringArray("res://addons/FreeControl/plugin.cfg", "res://addons/godot_object_serializer/plugin.cfg", "res://addons/phantom_camera/plugin.cfg") [global_group] @@ -103,12 +103,18 @@ reload={ , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":82,"key_label":0,"unicode":114,"location":0,"echo":false,"script":null) ] } -inventory={ +open_inventory={ "deadzone": 0.2, "events": [Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":2,"pressure":0.0,"pressed":true,"script":null) , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) ] } +ui_close_inventory={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +, Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":2,"pressure":0.0,"pressed":false,"script":null) +] +} [layer_names] diff --git a/godot/resources/player_inventory.tres b/godot/resources/player_inventory.tres index fd529ef..efeade5 100644 --- a/godot/resources/player_inventory.tres +++ b/godot/resources/player_inventory.tres @@ -1,10 +1,8 @@ -[gd_resource type="Resource" script_class="Inventory" load_steps=4 format=3 uid="uid://bllq6ri54q3ne"] +[gd_resource type="Resource" script_class="Inventory" load_steps=2 format=3 uid="uid://bllq6ri54q3ne"] -[ext_resource type="Script" uid="uid://bqprls343ue6e" path="res://src/item.gd" id="1_uptie"] [ext_resource type="Script" uid="uid://dh4ytedxidq0x" path="res://src/inventory.gd" id="2_1njko"] -[ext_resource type="Resource" uid="uid://cqfnwpmo4fyv4" path="res://resources/items/key.tres" id="2_85a8j"] [resource] script = ExtResource("2_1njko") -items = Array[ExtResource("1_uptie")]([ExtResource("2_85a8j"), null, null, null, null, null]) +max_capacity = null metadata/_custom_type_script = "uid://dh4ytedxidq0x" diff --git a/godot/scenes/inventory.tscn b/godot/scenes/inventory.tscn index 5e66e32..ea5eac3 100644 --- a/godot/scenes/inventory.tscn +++ b/godot/scenes/inventory.tscn @@ -15,8 +15,10 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +focus_mode = 2 script = ExtResource("1_qw0r6") -action_name = &"inventory" +open_action = &"inventory" +close_action = &"ui_close_inventory" [node name="ColorRect" type="ColorRect" parent="."] layout_mode = 1 @@ -27,7 +29,7 @@ grow_horizontal = 2 grow_vertical = 2 color = Color(0.10748, 0.10748, 0.10748, 1) -[node name="VBoxContainer" type="VBoxContainer" parent="." node_paths=PackedStringArray("item_list")] +[node name="VBoxContainer" type="VBoxContainer" parent="."] clip_contents = true layout_mode = 1 anchors_preset = 15 @@ -37,25 +39,16 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("2_hj2ta") inventory = ExtResource("3_ty45s") -item_list = NodePath("ItemNavigation/HItemList") [node name="ItemNavigation" type="HBoxContainer" parent="VBoxContainer"] layout_mode = 2 -[node name="MoveLeft" type="Button" parent="VBoxContainer/ItemNavigation"] -layout_mode = 2 -text = "<" - [node name="HItemList" type="HBoxContainer" parent="VBoxContainer/ItemNavigation"] layout_mode = 2 size_flags_horizontal = 3 script = ExtResource("4_yyk2a") item_scene = ExtResource("5_uae8j") -[node name="MoveRight" type="Button" parent="VBoxContainer/ItemNavigation"] -layout_mode = 2 -text = ">" - [node name="Details" type="HBoxContainer" parent="VBoxContainer" node_paths=PackedStringArray("icon_image", "description_label")] layout_mode = 2 size_flags_vertical = 3 @@ -91,7 +84,7 @@ grow_horizontal = 2 grow_vertical = 2 autowrap_mode = 2 -[connection signal="pressed" from="." to="." method="set_visible"] -[connection signal="pressed" from="VBoxContainer/ItemNavigation/MoveLeft" to="VBoxContainer/ItemNavigation/HItemList" method="move_left"] +[connection signal="closed" from="." to="." method="hide"] +[connection signal="opened" from="." to="." method="show"] +[connection signal="opened" from="." to="." method="grab_focus"] [connection signal="selected" from="VBoxContainer/ItemNavigation/HItemList" to="VBoxContainer/Details" method="_on_updated"] -[connection signal="pressed" from="VBoxContainer/ItemNavigation/MoveRight" to="VBoxContainer/ItemNavigation/HItemList" method="move_right"] diff --git a/godot/scenes/inventory2.tscn b/godot/scenes/inventory2.tscn new file mode 100644 index 0000000..36fed91 --- /dev/null +++ b/godot/scenes/inventory2.tscn @@ -0,0 +1,116 @@ +[gd_scene load_steps=10 format=3 uid="uid://cn7tgd4y67wnd"] + +[ext_resource type="Script" uid="uid://qvoqvonnxwfc" path="res://src/inventory_ui.gd" id="1_a0rpf"] +[ext_resource type="Script" uid="uid://bx4wxlm5mv268" path="res://src/root_control.gd" id="1_as33y"] +[ext_resource type="Resource" uid="uid://bllq6ri54q3ne" path="res://resources/player_inventory.tres" id="2_as33y"] +[ext_resource type="PackedScene" uid="uid://gn8k2ir47n1m" path="res://scenes/inventory_item.tscn" id="3_tg4gd"] +[ext_resource type="Script" uid="uid://13lxe4c4fmrp" path="res://addons/FreeControl/src/CustomClasses/Carousel/Carousel.gd" id="4_usnyx"] + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_as33y"] +size = Vector2(128, 128) + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_tg4gd"] +size = Vector2(128, 128) + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_usnyx"] +size = Vector2(128, 64) + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_sebc8"] +size = Vector2(256, 256) + +[node name="Inventory" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +focus_mode = 2 +script = ExtResource("1_as33y") +open_action = &"open_inventory" +close_action = &"ui_close_inventory" + +[node name="ColorRect" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0.147672, 0.147672, 0.147672, 1) + +[node name="Contents" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="InventoryUI" type="Control" parent="Contents" node_paths=PackedStringArray("carousel")] +layout_mode = 2 +size_flags_vertical = 3 +script = ExtResource("1_a0rpf") +inventory = ExtResource("2_as33y") +item_scene = ExtResource("3_tg4gd") +carousel = NodePath("Carousel") +metadata/_custom_type_script = "uid://qvoqvonnxwfc" + +[node name="Carousel" type="Container" parent="Contents/InventoryUI"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -104.0 +offset_top = 5.0 +offset_right = -104.0 +offset_bottom = 5.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("4_usnyx") +display_range = 4 +snap_behavior = 2 +paging_requirement = 100 +metadata/_custom_type_script = "uid://13lxe4c4fmrp" + +[node name="ItemDetails" type="HBoxContainer" parent="Contents"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="PlayerInfo" type="VBoxContainer" parent="Contents/ItemDetails"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="TextureRect" type="TextureRect" parent="Contents/ItemDetails/PlayerInfo"] +layout_mode = 2 +size_flags_horizontal = 4 +texture = SubResource("PlaceholderTexture2D_as33y") + +[node name="TextureRect2" type="TextureRect" parent="Contents/ItemDetails/PlayerInfo"] +layout_mode = 2 +size_flags_horizontal = 4 +texture = SubResource("PlaceholderTexture2D_tg4gd") + +[node name="TextureRect3" type="TextureRect" parent="Contents/ItemDetails/PlayerInfo"] +layout_mode = 2 +size_flags_horizontal = 4 +texture = SubResource("PlaceholderTexture2D_usnyx") + +[node name="Display" type="VBoxContainer" parent="Contents/ItemDetails"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="TextureRect" type="TextureRect" parent="Contents/ItemDetails/Display"] +layout_mode = 2 +size_flags_horizontal = 4 +texture = SubResource("PlaceholderTexture2D_sebc8") + +[node name="Description" type="VBoxContainer" parent="Contents/ItemDetails"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Label" type="Label" parent="Contents/ItemDetails/Description"] +layout_mode = 2 +text = "a description" diff --git a/godot/scenes/level.tscn b/godot/scenes/level.tscn index 905ae20..8de5c1b 100644 --- a/godot/scenes/level.tscn +++ b/godot/scenes/level.tscn @@ -84,6 +84,7 @@ shape = SubResource("BoxShape3D_mx8sn") script = ExtResource("3_w8frs") [node name="CanvasLayer" type="CanvasLayer" parent="."] +follow_viewport_enabled = true [node name="Control" type="Control" parent="CanvasLayer"] layout_mode = 3 @@ -110,6 +111,7 @@ text = "Load " [node name="Control2" parent="CanvasLayer" instance=ExtResource("8_b121j")] +visible = true [connection signal="interacted" from="Door/Interactable" to="Door" method="_on_interact"] [connection signal="pressed" from="CanvasLayer/Control/HBoxContainer/SaveButton" to="CanvasLayer/Control/Persistence" method="save" binds= ["save1.sav"]] diff --git a/godot/src/input_listener.gd b/godot/src/input_listener.gd index 2b49c25..74553e7 100644 --- a/godot/src/input_listener.gd +++ b/godot/src/input_listener.gd @@ -1,15 +1,29 @@ -class_name InputListener extends Node +class_name InputListener extends Control -signal pressed(toggle_state: bool) -signal released(toggle_state: bool) +enum ActionEvent { + Pressed, + Released +} -@export var toggle_state: bool -@export var action_name: StringName +signal opened +signal closed -func _input(event: InputEvent) -> void: - if event.is_action_pressed(action_name): - self.toggle_state = !toggle_state - pressed.emit(toggle_state) +@export var input_event: ActionEvent +@export var open_action: StringName +@export var close_action: StringName - if event.is_action_released(action_name): - released.emit(toggle_state) +func _is_active(event: InputEvent, action: StringName) -> bool: + match input_event: + ActionEvent.Pressed: return event.is_action_pressed(action) + ActionEvent.Released: return event.is_action_released(action) + _: return false + +func _unhandled_input(event: InputEvent) -> void: + if _is_active(event, open_action): + accept_event() + opened.emit() + +func _gui_input(event: InputEvent) -> void: + if _is_active(event, close_action): + accept_event() + closed.emit() diff --git a/godot/src/inventory.gd b/godot/src/inventory.gd index 48500eb..8b78816 100644 --- a/godot/src/inventory.gd +++ b/godot/src/inventory.gd @@ -1,17 +1,63 @@ class_name Inventory extends Resource -@export var items: Array[Item] +var initial_items: Array[ItemInstance] +var items: Dictionary[RID, ItemInstance] +@export var max_capacity: int = 6 + +var size: int: + get: return items.size() + +signal item_added(item: Item, quantity: int) +signal item_removed(item: Item, remaining: int) +signal updated + +func _init(): + call_deferred("_ready") + +func _ready(): + print(initial_items.size()) + for item in initial_items: + items[item.get_rid()] = item + print('setting', item) + +func _item_eq(a: Item, b: ItemInstance) -> bool: + return a.item == b + +func has_item(item: Item) -> bool: + return items.has(item.get_rid()) + +func find(item: Item) -> Option: + match items.get(item.get_rid()): + null: return Option.none + var found: return Option.some(found) + +func add_item(item: Item, quantity: int = 1): + var rid = item.get_rid() + var inst = items.get_or_add(rid) + inst.quantity += quantity + item_added.emit(item, inst.quantity) + updated.emit() + +func remove_item(item: Item, quantity: int = 1): + if find(item): + item.quantity -= quantity + if item.quantity <= 0: + items.erase(item.get_rid()) + item_removed.emit(item, item.quantity) + updated.emit() func _iter_continue(iter: Array) -> bool: - return iter[0] < len(items) + return iter[0].size() func _iter_init(iter: Array) -> bool: - iter[0] = 0 + iter[0] = items.keys() return _iter_continue(iter) func _iter_next(iter: Array) -> bool: iter[0] += 1 return _iter_continue(iter) -func _iter_get(iter: Variant) -> Item: - return items[iter] +func _iter_get(iter: Variant) -> ItemInstance: + var rid = iter[0] + iter.remove_at(0) + return items.get(rid) diff --git a/godot/src/inventory_ui.gd b/godot/src/inventory_ui.gd index 2feeafa..115bd15 100644 --- a/godot/src/inventory_ui.gd +++ b/godot/src/inventory_ui.gd @@ -1,13 +1,41 @@ -class_name InventoryUI extends VBoxContainer +class_name InventoryUI extends Control @export var inventory: Inventory -@export var item_list: HItemList +@export var item_scene: PackedScene +@export var carousel: Carousel func _ready() -> void: - item_list.add_items(inventory.items) + _build_carousel() + inventory.updated.connect(_build_carousel) -func _input(event: InputEvent) -> void: +func _build_carousel(): + Utils.remove_children(carousel) + + for instance in inventory: + print('pussy', instance) + var scene = create_item() + bind_item(scene, Option.from(instance.item)) + carousel.add_child(scene) + +func create_item() -> Node: + return item_scene.instantiate() + +func bind_item(node: Node, item: Option): + if node.has_method('bind'): + node.bind(item) + +func _gui_input(event: InputEvent) -> void: if event.is_action_pressed(PlayerInput.UIAction.Right): - item_list.move_right() + move_right() elif event.is_action_pressed(PlayerInput.UIAction.Left): - item_list.move_left() + move_left() + +func move_by(delta: int): + var next_index = carousel.get_carousel_index() + delta + carousel.go_to_index(next_index) + +func move_right(): + move_by(1) + +func move_left(): + move_by(-1) diff --git a/godot/src/item_instance.gd b/godot/src/item_instance.gd new file mode 100644 index 0000000..0c215d3 --- /dev/null +++ b/godot/src/item_instance.gd @@ -0,0 +1,24 @@ +class_name ItemInstance extends Resource + +@export var item: Item +@export var quantity: int + +@warning_ignore("shadowed_variable") +func _init(item: Item = null, quantity: int = 1) -> void: + self.item = item + self.quantity = quantity + +@warning_ignore("shadowed_variable") +func add_quantity(quantity: int = 1) -> int: + self.quantity += quantity + return self.quantity + +@warning_ignore("shadowed_variable") +func remove_quantity(quantity: int = 1) -> int: + self.quantity -= quantity + return self.quantity + +@warning_ignore("shadowed_variable") +func set_quantity(quantity: int) -> int: + self.quantity = quantity + return self.quantity diff --git a/godot/src/item_ui.gd b/godot/src/item_ui.gd index 6a2f68a..5f803d3 100644 --- a/godot/src/item_ui.gd +++ b/godot/src/item_ui.gd @@ -6,7 +6,7 @@ class_name ItemUI extends Control var _default_text: String var _default_icon: Texture2D -func _ready() -> void: +func _init() -> void: _default_text = name_label.text _default_icon = icon_texture.texture @@ -19,5 +19,6 @@ func bind(_item: Option): icon_texture.texture = item.icon func unbind(): + print(_default_text) name_label.text = _default_text icon_texture.texture = _default_icon diff --git a/godot/src/player_input.gd b/godot/src/player_input.gd index 5d19272..a6171e6 100644 --- a/godot/src/player_input.gd +++ b/godot/src/player_input.gd @@ -107,7 +107,7 @@ func _get_device(event: InputEvent) -> Device: else: return Device.Unknown -func _input(event: InputEvent) -> void: +func _unhandled_input(event: InputEvent) -> void: last_known_device = _get_device(event) if event.is_action_pressed(GameAction.Interact): diff --git a/godot/src/root_control.gd b/godot/src/root_control.gd new file mode 100644 index 0000000..bf7c0d7 --- /dev/null +++ b/godot/src/root_control.gd @@ -0,0 +1,23 @@ +class_name RootControl extends InputListener + +func _ready() -> void: + if self.focus_mode == Control.FOCUS_NONE: + self.focus_mode = Control.FOCUS_ALL + + opened.connect(open) + closed.connect(close) + + if visible: + grab_focus() + +func open(): + show() + grab_focus() + +func close(): + hide() + release_focus() + +func _gui_input(event: InputEvent) -> void: + super(event) + Utils.propagate_input_event(self, "_gui_input", event, false)