├── .gitignore ├── LICENSE ├── README.md ├── addons └── form │ ├── LICENSE │ ├── README.md │ ├── icon.svg │ ├── icon.svg.import │ ├── icons │ ├── ApiProtocol.svg │ ├── ApiProtocol.svg.import │ ├── Boundaries.svg │ ├── Boundaries.svg.import │ ├── FileProtocol.svg │ ├── FileProtocol.svg.import │ ├── Form.import │ ├── Form.svg │ ├── Form.svg.import │ ├── FormLabel.svg │ ├── FormLabel.svg.import │ ├── HttpProtocol.svg │ ├── HttpProtocol.svg.import │ ├── ListFilter.svg │ ├── ListFilter.svg.import │ ├── MailProtocol.svg │ ├── MailProtocol.svg.import │ ├── MailsendSmtpMailProtocol.svg │ ├── MailsendSmtpMailProtocol.svg.import │ ├── NetworkProtocol.svg │ ├── NetworkProtocol.svg.import │ ├── Protocol.svg │ ├── Protocol.svg.import │ ├── SmtpMailProtocol.svg │ ├── SmtpMailProtocol.svg.import │ ├── Submit.svg │ ├── Submit.svg.import │ ├── ValidatableLineEdit.svg │ ├── ValidatableLineEdit.svg.import │ ├── ValidatableTextEdit.svg │ ├── ValidatableTextEdit.svg.import │ ├── Validator.svg │ └── Validator.svg.import │ ├── nodes │ ├── Boundaries.gd │ ├── Form.gd │ ├── FormLabel.gd │ ├── ListFilter.gd │ ├── Protocol.gd │ ├── Protocols │ │ ├── FileProtocol.gd │ │ ├── HttpProtocol.gd │ │ ├── MailProtocol.gd │ │ ├── MailsendSmtpMailProtocol.gd │ │ ├── NetworkProtocol.gd │ │ └── SmtpMailProtocol.gd │ ├── Submit.gd │ ├── ValidatableLineEdit.gd │ ├── ValidatableTextEdit.gd │ └── Validator.gd │ ├── plugin.cfg │ ├── plugin.gd │ └── readme images │ ├── Download.png │ ├── Download.png.import │ ├── Editor.png │ ├── Editor.png.import │ ├── Enable.png │ ├── Enable.png.import │ ├── Game.png │ ├── Game.png.import │ ├── Game_400.png │ └── Game_400.png.import ├── icon.png ├── icon.svg └── test ├── .gitkeep ├── Test.tscn ├── nodes ├── Boundaries.test.gd ├── Form.test.gd ├── FormLabel.test.gd ├── ListFilter.test.gd ├── Protocol.test.gd ├── Protocols │ ├── FileProtocol.test.gd │ ├── HttpProtocol.test.gd │ ├── MailProtocol.test.gd │ ├── MailsendSmtpMailProtocol.test.gd │ ├── NetworkProtocol.test.gd │ └── SmtpMailProtocol.test.gd ├── Submit.test.gd ├── ValidatableLineEdit.test.gd ├── ValidatableTextEdit.test.gd └── Validator.test.gd ├── plugin.test.gd └── test.gd /.gitignore: -------------------------------------------------------------------------------- 1 | # for some reason * doesn't work so I have to use *.* 2 | *.* 3 | !addons/form/**/* 4 | !test/**/* 5 | !LICENSE 6 | !README* 7 | !icon.svg 8 | !icon.png 9 | 10 | # Godot 4+ specific ignores 11 | .godot/ 12 | 13 | # Godot-specific ignores 14 | .import/ 15 | export.cfg 16 | export_presets.cfg 17 | 18 | # Imported translations (automatically generated from CSV files) 19 | *.translation 20 | 21 | # Mono-specific ignores 22 | .mono/ 23 | data_*/ 24 | mono_crash.*.json 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Moritz Tim W. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Icon
3 | Godot Form Plugin 4 |

5 | 6 | ## Example 7 | ### In the Editor 8 | This is how a form might look in the editor:
9 | ![Form in the editor](addons/form/readme%20images/Editor.png) 10 | ### In Action 11 | This is how the form might look in the game:
12 | ![Form in action](addons/form/readme%20images/Game_400.png) 13 | 14 | ## Requirements 15 | - Godot 4.1 or higher 16 | > Godot 4.0 might work, but is not tested. 17 | - For [`MailsendSmtPMailProtocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#mailsendsmtpmailprotocol-smtpmailprotocol-): [mailsend-go](https://github.com/muquit/mailsend-go) or [mailsend](https://github.com/muquit/mailsend) 18 | 19 | ## Quick Start 20 | Get a form up and running in 11 steps. 21 | ### Installing the plugin 22 | 1. Install the plugin from the Asset Library tab in Godot or from the [Asset Library Page](https://godotengine.org/asset-library/asset/2362) or by following the [Installation](https://github.com/moritz-t-w/Godot-Form/wiki/Installation) instructions. When the following prompt comes up, select only the "addons" folder:
23 | ![Download only the addons folder](addons/form/readme%20images/Download.png) 24 | 2. Enable the plugin in the Project Settings:
25 | ![Enable the plugin](addons/form/readme%20images/Enable.png) 26 | ### Creating the structure 27 | 3. Add a [`Form`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#Form) to your scene. 28 | 4. Add a `Container` of your choice as a child of the [`Form`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#Form) to hold the form elements. 29 | 5. For each form element, add a [`FormLabel`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#formlabel--label) and any input `Control`. 30 | 6. Finally, add a [`Submit`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#submit) button. 31 | ### Hooking everything up 32 | 7. In the inspector of your [`Form`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#Form), set the [`Submit Button`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#submit_button-submit) property to your [`Submit`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#submit) button. 33 | 8. In the inspector of each element, set the [`input`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#input-control) property to the corresponding `Control` node. 34 | ### Configuring the form 35 | 9. In the inspector of your [`Form`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#Form), choose and set up a [`Protocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#protocol-resource). 36 | Currently supported protocols are: 37 | - [`HttpProtocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#httpprotocol-networkprotocol) 38 | - [`MailsendSmtPMailProtocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#mailsendsmtpmailprotocol-smtpmailprotocol-) 39 | - [`FileProtocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#fileprotocol-protocol) 40 | > Of course, you can also implement your own protocol by extending the [`Protocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#protocol-resource) class or any of its descendants. 41 | 10. In the inspector of each [`FormLabel`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#formlabel--label), set the [`input_required`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#input_required-false) property if needed. 42 | 11. In the inspector of each input `Control`, set and configure the [`Validator`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#validator-resource) property with rules. 43 | 44 | > More info in the [Wiki](https://github.com/moritz-t-w/Godot-Form/wiki) -------------------------------------------------------------------------------- /addons/form/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Moritz Tim W. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the Software), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /addons/form/README.md: -------------------------------------------------------------------------------- 1 |

2 | Icon
3 | Godot Form Plugin 4 |

5 | 6 | ## Example 7 | ### In the Editor 8 | This is how a form might look in the editor:
9 | ![Form in the editor](readme%20images/Editor.png) 10 | ### In Action 11 | This is how the form might look in the game:
12 | ![Form in action](readme%20images/Game_400.png) 13 | 14 | ## Requirements 15 | - Godot 4.1 or higher 16 | > Godot 4.0 might work, but is not tested. 17 | - For [`MailsendSmtPMailProtocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#mailsendsmtpmailprotocol-smtpmailprotocol-): [mailsend-go](https://github.com/muquit/mailsend-go) or [mailsend](https://github.com/muquit/mailsend) 18 | 19 | ## Quick Start 20 | Get a form up and running in 11 steps. 21 | ### Installing the plugin 22 | 1. Install the plugin from the Asset Library tab in Godot or from the [Asset Library Page](https://godotengine.org/asset-library/asset/2362) or by following the [Installation](https://github.com/moritz-t-w/Godot-Form/wiki/Installation) instructions. When the following prompt comes up, select only the "addons" folder:
23 | ![Download only the addons folder](readme%20images/Download.png) 24 | 2. Enable the plugin in the Project Settings:
25 | ![Enable the plugin](readme%20images/Enable.png) 26 | ### Creating the structure 27 | 3. Add a [`Form`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#Form) to your scene. 28 | 4. Add a `Container` of your choice as a child of the [`Form`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#Form) to hold the form elements. 29 | 5. For each form element, add a [`FormLabel`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#formlabel--label) and any input `Control`. 30 | 6. Finally, add a [`Submit`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#submit) button. 31 | ### Hooking everything up 32 | 7. In the inspector of your [`Form`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#Form), set the [`Submit Button`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#submit_button-submit) property to your [`Submit`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#submit) button. 33 | 8. In the inspector of each element, set the [`input`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#input-control) property to the corresponding `Control` node. 34 | ### Configuring the form 35 | 9. In the inspector of your [`Form`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#Form), choose and set up a [`Protocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#protocol-resource). 36 | Currently supported protocols are: 37 | - [`HttpProtocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#httpprotocol-networkprotocol) 38 | - [`MailsendSmtPMailProtocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#mailsendsmtpmailprotocol-smtpmailprotocol-) 39 | - [`FileProtocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#fileprotocol-protocol) 40 | > Of course, you can also implement your own protocol by extending the [`Protocol`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#protocol-resource) class or any of its descendants. 41 | 10. In the inspector of each [`FormLabel`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#formlabel--label), set the [`input_required`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#input_required-false) property if needed. 42 | 11. In the inspector of each input `Control`, set and configure the [`Validator`](https://github.com/moritz-t-w/Godot-Form/wiki/Code-Reference#validator-resource) property with rules. 43 | 44 | > More info in the [Wiki](https://github.com/moritz-t-w/Godot-Form/wiki) -------------------------------------------------------------------------------- /addons/form/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://kh2njbs5ppvf" 6 | path="res://.godot/imported/icon.svg-03070a30f642dbb80acc9e062b176d81.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-03070a30f642dbb80acc9e062b176d81.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/ApiProtocol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/ApiProtocol.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://byrhcdgstke8h" 6 | path="res://.godot/imported/ApiProtocol.svg-2cb4b771304011e58870e75cf1534712.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/ApiProtocol.svg" 14 | dest_files=["res://.godot/imported/ApiProtocol.svg-2cb4b771304011e58870e75cf1534712.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/Boundaries.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/Boundaries.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dxngwdk2x56j7" 6 | path="res://.godot/imported/Boundaries.svg-5cfb518ffd3ef3da847b2585bf7e4177.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/Boundaries.svg" 14 | dest_files=["res://.godot/imported/Boundaries.svg-5cfb518ffd3ef3da847b2585bf7e4177.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/FileProtocol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/FileProtocol.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bwrvxdrpdct1q" 6 | path="res://.godot/imported/FileProtocol.svg-79f24d48ebe189a6518d3b0a8663ea81.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/FileProtocol.svg" 14 | dest_files=["res://.godot/imported/FileProtocol.svg-79f24d48ebe189a6518d3b0a8663ea81.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/Form.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://nvp45g17tn43" 6 | path="res://.godot/imported/Form.svg-e5754cb1379f506b4ba32258645049dd.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/Form.svg" 14 | dest_files=["res://.godot/imported/Form.svg-e5754cb1379f506b4ba32258645049dd.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/Form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/Form.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bucxtx0hbawme" 6 | path="res://.godot/imported/Form.svg-abea5fa1fe94a5cd69ae4059216c9afe.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/Form.svg" 14 | dest_files=["res://.godot/imported/Form.svg-abea5fa1fe94a5cd69ae4059216c9afe.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/FormLabel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/FormLabel.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://plbq31j1n1cn" 6 | path="res://.godot/imported/FormLabel.svg-f8f82b2681a1627b7c341a87bc537853.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/FormLabel.svg" 14 | dest_files=["res://.godot/imported/FormLabel.svg-f8f82b2681a1627b7c341a87bc537853.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/HttpProtocol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/HttpProtocol.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://l6kuynmuuutd" 6 | path="res://.godot/imported/HttpProtocol.svg-12e35ba70e724f7492dba9fc66585a52.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/HttpProtocol.svg" 14 | dest_files=["res://.godot/imported/HttpProtocol.svg-12e35ba70e724f7492dba9fc66585a52.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/ListFilter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/ListFilter.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b1mxr6mr803c3" 6 | path="res://.godot/imported/ListFilter.svg-b191fdd5230801d6595829da20ab9d65.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/ListFilter.svg" 14 | dest_files=["res://.godot/imported/ListFilter.svg-b191fdd5230801d6595829da20ab9d65.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/MailProtocol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/MailProtocol.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d3st07i8w4ety" 6 | path="res://.godot/imported/MailProtocol.svg-24c3695e39c29be9573af2b21a913efb.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/MailProtocol.svg" 14 | dest_files=["res://.godot/imported/MailProtocol.svg-24c3695e39c29be9573af2b21a913efb.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/MailsendSmtpMailProtocol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/MailsendSmtpMailProtocol.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://rt8snty1dxl2" 6 | path="res://.godot/imported/MailsendSmtpMailProtocol.svg-e327311ae2d6b91b6793c1452309f8d5.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/MailsendSmtpMailProtocol.svg" 14 | dest_files=["res://.godot/imported/MailsendSmtpMailProtocol.svg-e327311ae2d6b91b6793c1452309f8d5.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/NetworkProtocol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/NetworkProtocol.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://domubjnsto53u" 6 | path="res://.godot/imported/NetworkProtocol.svg-53c21b59eeac4ea38f3431b38ec2cb50.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/NetworkProtocol.svg" 14 | dest_files=["res://.godot/imported/NetworkProtocol.svg-53c21b59eeac4ea38f3431b38ec2cb50.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/Protocol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/Protocol.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cu5r1h8ftnt11" 6 | path="res://.godot/imported/Protocol.svg-e4bf55fdaaa2a0c59d7b7acddbd7fc8e.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/Protocol.svg" 14 | dest_files=["res://.godot/imported/Protocol.svg-e4bf55fdaaa2a0c59d7b7acddbd7fc8e.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/SmtpMailProtocol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/SmtpMailProtocol.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cbym1v3ebuksf" 6 | path="res://.godot/imported/SmtpMailProtocol.svg-4ec97ea1c6c99b42258a3a0f3b45ab3e.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/SmtpMailProtocol.svg" 14 | dest_files=["res://.godot/imported/SmtpMailProtocol.svg-4ec97ea1c6c99b42258a3a0f3b45ab3e.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/Submit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/Submit.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://csydvp2y5qc7e" 6 | path="res://.godot/imported/Submit.svg-acf33bfa0f68f26481b5e0142110f00d.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/Submit.svg" 14 | dest_files=["res://.godot/imported/Submit.svg-acf33bfa0f68f26481b5e0142110f00d.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/ValidatableLineEdit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/ValidatableLineEdit.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://df5v3yvfc7dm8" 6 | path="res://.godot/imported/ValidatableLineEdit.svg-6103e8a91b8ec66b294f0020abf729d1.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/ValidatableLineEdit.svg" 14 | dest_files=["res://.godot/imported/ValidatableLineEdit.svg-6103e8a91b8ec66b294f0020abf729d1.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/ValidatableTextEdit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/ValidatableTextEdit.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cot4782xei8cq" 6 | path="res://.godot/imported/ValidatableTextEdit.svg-d8adc7b14ee7956296b2bdc048440a11.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/ValidatableTextEdit.svg" 14 | dest_files=["res://.godot/imported/ValidatableTextEdit.svg-d8adc7b14ee7956296b2bdc048440a11.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/icons/Validator.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /addons/form/icons/Validator.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://wb3kptoqowcg" 6 | path="res://.godot/imported/Validator.svg-50b11e24a1751bb36ced1e3b3cfe3e02.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/icons/Validator.svg" 14 | dest_files=["res://.godot/imported/Validator.svg-50b11e24a1751bb36ced1e3b3cfe3e02.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /addons/form/nodes/Boundaries.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | ## An upper and lower bound for an integer value 3 | class_name Boundaries extends Resource 4 | 5 | ## Lower bound 6 | @export var min: int: 7 | set(new_val): 8 | if new_val > 0: 9 | min = new_val 10 | else: 11 | min = 0 12 | if min > max: 13 | max = min 14 | ## Upper bound 15 | @export var max: int: 16 | set(new_val): 17 | if new_val > 0: 18 | max = new_val 19 | else: 20 | max = 0 21 | if max < min: 22 | min = max 23 | 24 | ## Determines if the subject is within the boundaries 25 | func has(subject: int) -> bool: 26 | return (max == 0||subject <= max)&&(min == 0||subject >= min) 27 | -------------------------------------------------------------------------------- /addons/form/nodes/Form.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | ## Manages form submission and is intended to contain the form elements and contains the is_input() method. 3 | class_name Form extends Control 4 | 5 | @export_group("Submission") 6 | ## Calls the submit() function when pressed. 7 | @export var submit_button: Submit: 8 | set(new_val): 9 | if new_val != null: 10 | new_val.pressed.connect(submit) 11 | else: 12 | submit_button.pressed.disconnect(submit) 13 | submit_button = new_val 14 | ## Handles the submission of the form. 15 | @export var protocol: Protocol 16 | 17 | ## Submits the form data to the protocol if the data is valid. 18 | func submit(): 19 | var fields := generate_fields_dict(true) 20 | var valid := true 21 | for field in fields.values(): 22 | if field["label"] != null&&!field["label"].indicate_validity(): 23 | valid = false 24 | if !valid: 25 | return 26 | protocol.submit(generate_fields_dict()) 27 | 28 | ## Generates a dictionary of the form data. 29 | ## Keys are generated from the node names. 30 | ## If a key with the same name already exists, the instance id is joined with an underscore to the key. 31 | ## Example of a key with instance id: "Input_29460792527" 32 | func generate_fields_dict( 33 | ## if include_labels: 34 | ## return { { "label": ..., "input": ... }, ... } 35 | ## else: 36 | ## return { input, ... } 37 | include_labels: bool=false, 38 | ## The node to generate the dictionary from. 39 | ## This is mainly used for recursion. 40 | subject: Node=self, 41 | ## The dictionary to add the fields to. 42 | ## This is mainly used for recursion. 43 | fields:={} 44 | ) -> Dictionary: 45 | var labeled_inputs := [] 46 | 47 | for child in subject.get_children(): 48 | # If the child is a label with an associated input, add it to the dictionary. 49 | if child is FormLabel&&child.input != null: 50 | var key := generate_unique_key(child, fields) 51 | if include_labels: 52 | fields[key] = { 53 | "label": child, 54 | "input": child.input, 55 | } 56 | else: 57 | fields[key] = child.input 58 | labeled_inputs.append(child.input) 59 | # Else if the child serves as a container, recursively add its children. 60 | elif child.get_child_count() > 0: 61 | # Add the child's children to the dictionary as they are returned from the recursive call. 62 | fields.merge(generate_fields_dict(include_labels, child, fields)) 63 | 64 | # Before we checked only inputs that have labels, so this adds the remaining ones. 65 | for child in subject.get_children(): 66 | # If it's an input and it hasn't been added yet, add it. 67 | if Form.is_input(child)&&!labeled_inputs.has(child): 68 | var key := generate_unique_key(child, fields) 69 | if include_labels: 70 | fields[key] = { 71 | "label": null, 72 | "input": child, 73 | } 74 | else: 75 | fields[key] = child 76 | return fields 77 | 78 | ## Generates a unique key for the subject to be used in the object. 79 | func generate_unique_key(subject: Node, object: Dictionary) -> StringName: 80 | var key := subject.name 81 | if subject is FormLabel: 82 | key = subject.text 83 | if key in object.keys(): # if there is already an input with this name, add the instance id to the key 84 | var id = subject.get_instance_id() 85 | key += &"_{0}".format([id]) # ensure StringName 86 | if key in object.keys(): 87 | printerr("Duplicate input instance id: " + str(id) + ". Overwriting.") 88 | return key 89 | 90 | ## Returns whether the given node is an input. 91 | ## Inputs are: 92 | ## - buttons except MenuButton 93 | ## - LineEdit 94 | ## - TextEdit 95 | ## - ItemList 96 | ## - Slider 97 | ## - SpinBox 98 | ## - GraphEdit 99 | static func is_input(subject: Node) -> bool: 100 | return ( 101 | # subject is input button 102 | ( 103 | subject is BaseButton 104 | # meaning a button, but not one that just opens a popup 105 | &&!subject is MenuButton 106 | ) 107 | # or subject is input field 108 | ||subject is LineEdit 109 | ||subject is TextEdit 110 | ||subject is ItemList 111 | ||subject is Slider 112 | ||subject is SpinBox 113 | ||subject is GraphEdit 114 | ) 115 | -------------------------------------------------------------------------------- /addons/form/nodes/FormLabel.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | ## Label for input controls 3 | class_name FormLabel extends Label 4 | 5 | ## Input control to label 6 | @export var input: Control: 7 | set(new_val): 8 | if new_val == null||Form.is_input(new_val): 9 | if input != null: 10 | input.gui_input.disconnect(_on_gui_input) 11 | input = new_val 12 | mode = mode # run setter 13 | indicate_required() 14 | if validate_on_input&&input != null: 15 | input.gui_input.connect(_on_gui_input) 16 | else: 17 | printerr(get_class(), ": input must be a input button or input field") 18 | ## "Input value must not be empty" 19 | @export var input_required := false: 20 | set(new_val): 21 | input_required = new_val 22 | indicate_required() 23 | 24 | ## Indicate validity when the input node recieves a GUI input 25 | @export var validate_on_input := true 26 | 27 | @export_group("Input Display") 28 | 29 | ## The style to apply to the input when invalid 30 | @export var invalid_style: StyleBox 31 | 32 | ## The style to apply to the input when valid 33 | var valid_style: StyleBox: 34 | get: 35 | if input&&_valid_style == null: 36 | _valid_style = input.get_theme_stylebox("normal") 37 | return _valid_style 38 | ## Internal storage for valid_style 39 | var _valid_style 40 | 41 | @export_group("Label Display") 42 | 43 | ## String to append to label if input_required 44 | @export var required_hint := "*" 45 | 46 | enum Mode { 47 | ## Display the text as the label.text 48 | SEPARATE, 49 | ## Hide the label and overwrite input.placeholder_text or for lack thereof, input.text 50 | IN_INPUT, 51 | ## Hide the label 52 | HIDDEN 53 | } 54 | ## How to display the label 55 | @export var mode := Mode.SEPARATE: 56 | set(new_val): 57 | if new_val == null: 58 | new_val = Mode.SEPARATE 59 | mode = new_val 60 | 61 | if mode == Mode.SEPARATE: 62 | visible = true 63 | else: 64 | visible = false 65 | if input != null: 66 | var found := false 67 | for prop in ["placeholder_text", "text"]: 68 | if has_property(input, prop): 69 | if mode == Mode.IN_INPUT: 70 | if input[prop] != text: 71 | input_text_backup = input[prop] 72 | input[prop] = text 73 | elif input[prop] == text: 74 | input[prop] = input_text_backup 75 | found = true 76 | break 77 | if !found&&mode == Mode.IN_INPUT: 78 | push_error("Input ", input.get_instance_id(), " has no placeholder_text or text property") 79 | mode = Mode.SEPARATE 80 | visible = true 81 | 82 | var input_text_backup := "" 83 | 84 | ## Sets the label text to the input's name if it is empty and runs necessary setters 85 | func _enter_tree(): 86 | if input != null&&text in [null, ""]: 87 | text = input.name 88 | if !visibility_changed.is_connected(update_display_mode): 89 | visibility_changed.connect(update_display_mode) 90 | 91 | func _ready(): 92 | indicate_required() 93 | 94 | ## Update label display mode based on visibility 95 | ## If the label is visible and the mode is either Mode.IN_INPUT or Mode.HIDDEN, the mode is set to Mode.SEPARATE. 96 | ## If the label is not visible and the mode is Mode.SEPARATE, the mode is set to Mode.HIDDEN. 97 | ## Else the mode setter is run (mode = mode). 98 | func update_display_mode(): 99 | if visible&&(mode == Mode.IN_INPUT||mode == Mode.HIDDEN): 100 | mode = Mode.SEPARATE 101 | elif !visible&&mode == Mode.SEPARATE: 102 | mode = Mode.HIDDEN 103 | else: 104 | # run setter 105 | mode = mode 106 | 107 | ## Add or remove the required_hint if input_required 108 | func indicate_required(): 109 | if !is_node_ready(): 110 | return 111 | # if * needed but not present 112 | if input_required: 113 | if required_hint not in ["", null]&&!text.ends_with(required_hint): 114 | # add 115 | text += required_hint 116 | # if * present but not needed 117 | elif text.ends_with(required_hint): 118 | # remove 119 | text = text.left(text.length() - required_hint.length()) 120 | mode = mode # run setter 121 | 122 | ## Change style based on validity and return validity or default if input is not validatable 123 | func indicate_validity( 124 | ## the default value to return if input is not validatable 125 | default:=true 126 | ) -> bool: 127 | var valid = default 128 | # no input = not validatable -> valid = default 129 | if input: 130 | var broken_rules := {} 131 | var value = Protocol.new().get_value(input) 132 | var input_has_not_null_validator = has_property(input, "validator")&&input.validator != null 133 | 134 | if input_required&&(value == null||((value is String||value is StringName)&&value == "")): 135 | # input is required but empty -> valid = false 136 | broken_rules["required"] = true 137 | valid = false 138 | # else: valid = default, but that's already done 139 | # has text and validator -> valid = validate() 140 | elif input_has_not_null_validator: 141 | valid = input.validator.validate(input.text) 142 | if !valid: 143 | broken_rules = input.validator.broken_rules 144 | else: # Has a text value or doesn't have to be true, has no validation rules. -> valid = true 145 | valid = true 146 | 147 | if !valid: 148 | print("Input ", input.get_instance_id(), " breaks the following rule(s):") 149 | print(broken_rules) 150 | 151 | if invalid_style != null: 152 | valid_style # run getter 153 | var style = invalid_style 154 | if valid: 155 | style = valid_style 156 | input.add_theme_stylebox_override("normal", style) 157 | else: 158 | var msg = "No invalid_style set" 159 | if !valid: 160 | push_warning(msg) 161 | else: 162 | print(msg) 163 | return valid 164 | 165 | ## Return validity of "Subject exist, has property_name and property_name is not a method" 166 | func has_property(subject: Object, property_name: StringName) -> bool: 167 | if subject == null: 168 | return false 169 | return property_name in subject&&!subject.has_method(property_name) 170 | 171 | ## Indicate validity on GUI input if event is relevant and validate_on_input 172 | func _on_gui_input(event: InputEvent): 173 | if validate_on_input&&!(event is InputEventMouseMotion)&&Form.is_input(input)&&( 174 | event is InputEventMouseButton&&event.button_index == MOUSE_BUTTON_LEFT&&!event.pressed&&( # left click release 175 | input is BaseButton 176 | ||input is Slider 177 | ||input is SpinBox 178 | ||input is GraphEdit 179 | ) 180 | ||event is InputEventKey 181 | ): 182 | indicate_validity.call_deferred() # ensure value is updated before validation 183 | -------------------------------------------------------------------------------- /addons/form/nodes/ListFilter.gd: -------------------------------------------------------------------------------- 1 | ## A filter with a blacklist or whitelist of strings 2 | class_name ListFilter extends Resource 3 | 4 | enum Match { 5 | ## All elements must be present 6 | ALL = 1, 7 | ## At least one element must be present 8 | AT_LEAST_ONE = 0 9 | } 10 | ## Match requirement 11 | @export var match: Match 12 | ## The blacklist or whitelist 13 | @export var elements: Array[String] 14 | 15 | ## Returns wether the subject is represented in the list 16 | func is_represented_in(subject: String) -> bool: 17 | for element in elements: 18 | if subject.contains(element): 19 | if !bool(match ): # If any element is present and AT_LEAST_ONE must be, return true 20 | return true 21 | ## else, keep looking 22 | elif bool(match ): # If any element is not present but ALL must be, return false 23 | return false 24 | # If we get here, either every element is present and ALL must be, or no element is present and AT_LEAST_ONE must be 25 | return bool(match ) 26 | 27 | ## Returns the output of elements.size() 28 | func size() -> int: 29 | return elements.size() 30 | -------------------------------------------------------------------------------- /addons/form/nodes/Protocol.gd: -------------------------------------------------------------------------------- 1 | ## Handles form submission and response. 2 | class_name Protocol extends Resource 3 | 4 | ## Characters other than escape characters that can do bad things within a quoted shell command argument. 5 | const SHELL_BLACKLIST = "\"$%`!" 6 | 7 | ## Characters that are probably fine inside a quoted shell command argument. 8 | const SHELL_WHITELIST = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_,.-+=@#/()'" 9 | 10 | @export_group("Security") 11 | enum Sanitization { 12 | ## No sanitization is performed. 13 | ## Only do this if you are holding your user at gunpoint to ensure they don't do anything malicious. 14 | NONE, 15 | ## Metacharacters that work inside quotes are escaped. 16 | ## This might not be completely safe. 17 | SHELL_ESCAPE, 18 | ## Metacharacters that work inside quotes are removed. 19 | SHELL_BLACKLIST, 20 | ## Only definetly safe characters are kept. 21 | ## See SHELL_WHITELIST 22 | SHELL_WHITELIST, 23 | ## Only Alphanumeric characters are kept. 24 | ALPHANUMERIC 25 | } 26 | ## How User Input is sanitized when the protocol deems it necessary. 27 | @export var sanitization: Sanitization = Sanitization.SHELL_WHITELIST 28 | 29 | ## Additional characters to remove from user input before sanitization. 30 | ## Example: "@#$%&!" 31 | @export var blacklist: String = "" 32 | 33 | ## Submits form data and returns HTTP status code of the response. 34 | func submit( 35 | ## The output of Form.generate_fields_dict(). 36 | fields: Dictionary 37 | ) -> int: 38 | push_error("not implemented") 39 | return - 1 40 | 41 | ## Finds the value of the given Node based on its type. 42 | ## Throws an error if the type is unknown. 43 | ## BaseButton -> button_pressed: bool 44 | ## LineEdit | TextEdit -> text: String 45 | ## Slider | SpinBox -> value: float 46 | ## GraphEdit -> get_connection_list(): Array[Dictionary] 47 | ## ItemList -> items: Array[{ 48 | ## selected = is_selected(): bool, 49 | ## text = get_item_text(): String, 50 | ## icon = get_item_icon(): Texture, 51 | ## metadata = get_item_metadata(): Variant 52 | ## }] 53 | func get_value(subject: Node) -> Variant: 54 | if subject is BaseButton: 55 | return subject.button_pressed 56 | elif subject is LineEdit||subject is TextEdit: 57 | return subject.text 58 | elif subject is ItemList: 59 | var items: Array[Dictionary] = [] 60 | for i in range(subject.get_item_count()): 61 | items.append({ 62 | "selected": subject.is_selected(i), 63 | "text": subject.get_item_text(i), 64 | "icon": subject.get_item_icon(i), 65 | "metadata": subject.get_item_metadata(i) 66 | }) 67 | return items 68 | elif subject is Slider||subject is SpinBox: 69 | return subject.value 70 | elif subject is GraphEdit: 71 | return subject.get_connection_list() 72 | else: 73 | push_error("Unknown input type: " + str(subject)) 74 | return null 75 | 76 | ## Sanitizes subject according to the selected sanitization method. 77 | func sanitize( 78 | ## Any Variant that needs to be sanitized. 79 | ## Dictionaries and Arrays are sanitized recursively. 80 | ## Any other type will be returned as is. 81 | subject: Variant, 82 | ## Stores every instance of every character that was caught by the sanitization in order of appearance. 83 | jail:=[], 84 | ## The sanitization method to use. 85 | sanitization_override:=sanitization, 86 | ## Whether to sanitize Dictionary keys. Note that this is passed down recursively. 87 | sanitize_keys:=false 88 | ) -> Variant: 89 | var sanitized := subject 90 | 91 | match typeof(subject): 92 | TYPE_DICTIONARY: 93 | sanitized = {} 94 | for key in subject.keys(): 95 | var sub_subject = subject[key] 96 | if sanitize_keys: 97 | key = sanitize(key, jail) 98 | sanitized[key] = sanitize(sub_subject, jail, sanitization_override, sanitize_keys) 99 | TYPE_ARRAY: 100 | sanitized = [] 101 | for item in subject: 102 | sanitized.append(sanitize(item, jail, sanitization_override, sanitize_keys)) 103 | TYPE_STRING: # This is where the magic happens 104 | sanitized = "" 105 | # Blacklist 106 | if blacklist not in [null, ""]: 107 | var original_subject = subject 108 | subject = "" 109 | for char in original_subject: 110 | if char in blacklist: 111 | jail.append(char) 112 | else: 113 | subject += char 114 | 115 | # Sanitization 116 | var escape_char = "\\" 117 | if OS.get_name() == "Windows": 118 | escape_char = "^" # Windows is not like the other kids 119 | var os_specific_blacklist = "".join([SHELL_BLACKLIST, escape_char]) 120 | match sanitization_override: 121 | Sanitization.NONE: 122 | sanitized = subject 123 | Sanitization.SHELL_ESCAPE: 124 | for char in subject: 125 | if char in os_specific_blacklist: # if it's a naughty char 126 | sanitized += escape_char # add escape char 127 | jail.append(char) 128 | sanitized += char # add char 129 | Sanitization.SHELL_BLACKLIST: 130 | for char in subject: 131 | if !(char in os_specific_blacklist): # if allowed 132 | sanitized += char # add char 133 | else: 134 | jail.append(char) 135 | Sanitization.SHELL_WHITELIST: 136 | for char in subject: 137 | if char in SHELL_WHITELIST: 138 | sanitized += char # add char 139 | else: 140 | jail.append(char) # lock char away 141 | Sanitization.ALPHANUMERIC: 142 | var regex := RegEx.new() 143 | regex.compile("[^a-zA-Z0-9]") # non alphanumeric 144 | var result := regex.search_all(subject) 145 | var matches := [] 146 | for match in result: 147 | var matchstr = match .get_string() 148 | matches.append(matchstr) 149 | jail.append(matchstr) 150 | for char in subject: 151 | if !(char in matches): 152 | sanitized += char 153 | var size = jail.size() 154 | if size > 0: 155 | print("Protocol.sanitze() found ", size, " illegal characters in subject") 156 | return sanitized 157 | -------------------------------------------------------------------------------- /addons/form/nodes/Protocols/FileProtocol.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | ## Handles form submission to a JSON file. 3 | class_name FileProtocol extends Protocol 4 | 5 | ## Directory to save responses to. 6 | @export_global_dir var target_dir 7 | 8 | ## File name scheme. 9 | ## Available variables: 10 | ## - {hash}: Hash of the form data. 11 | ## - {id}: Number of existing files in the target directory + 1. 12 | ## - {year}: Current year. 13 | ## - {month}: Current month. 14 | ## - {day}: Current day. 15 | ## - {weekday}: Current weekday. 16 | ## - {hour}: Current hour. 17 | ## - {minute}: Current minute. 18 | ## - {second}: Current second. 19 | @export var file_name_scheme := "{id}.json" 20 | 21 | ## Saves form data to a new file according to the file_name_scheme and returns 200. 22 | func submit( 23 | ## The output of Form.generate_fields_dict(). 24 | fields: Dictionary 25 | ) -> int: 26 | if (target_dir == ""||target_dir == null): 27 | push_error("No target directory specified.") 28 | return 0 29 | 30 | if DirAccess.dir_exists_absolute(target_dir) == false: 31 | push_error("Target directory does not exist.") 32 | return 0 33 | 34 | var file_name := generate_file_name(fields) 35 | 36 | if FileAccess.file_exists(target_dir + "/" + file_name): 37 | push_error("File already exists.") 38 | return 0 39 | 40 | var values = fields.values().map(func(input: Node): 41 | return get_value(input) 42 | ) 43 | for key in fields.keys(): 44 | fields[key] = values.pop_front() 45 | 46 | var file := FileAccess.open(target_dir + "/" + file_name, FileAccess.WRITE) 47 | file.store_string(JSON.stringify(fields)) 48 | file.close() 49 | 50 | return 200 51 | 52 | func generate_file_name(fields: Dictionary) -> String: 53 | var submission_time := Time.get_date_dict_from_system() 54 | submission_time.merge(Time.get_time_dict_from_system()) 55 | var format_dict := { 56 | "id": DirAccess.get_files_at(target_dir).size() + 1, 57 | "hash": fields.hash() 58 | } 59 | format_dict.merge(submission_time) 60 | 61 | return file_name_scheme.format(format_dict) 62 | -------------------------------------------------------------------------------- /addons/form/nodes/Protocols/HttpProtocol.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | ## Handles form submission and response over the network using HyperText Transfer Protocol 3 | ## Web export is not supported. 4 | ## Based on tutorial https://docs.godotengine.org/en/stable/tutorials/networking/http_client_class.html#http-client-class 5 | class_name HttpProtocol extends NetworkProtocol 6 | 7 | @export_group("Target") 8 | ## Use HTTPS 9 | @export var encrypt := true: 10 | set(new_val): 11 | encrypt = new_val 12 | if port in [- 1, 443, 80]: 13 | if encrypt: 14 | port = 443 15 | else: 16 | port = 80 17 | ## Target Path 18 | @export var path := "/" 19 | 20 | @export_group("Request") 21 | enum Method { 22 | GET, 23 | HEAD, 24 | POST, 25 | PUT, 26 | DELETE, 27 | OPTIONS, 28 | TRACE, 29 | CONNECT, 30 | PATCH 31 | } 32 | ## HTTP Method 33 | @export var method := Method.POST 34 | ## HTTP Headers 35 | @export var headers := {} 36 | 37 | ## HTTP Client 38 | var http := HTTPClient.new() 39 | 40 | ## {protocol}://{host} 41 | var base_url: String: 42 | get: 43 | var protocol := "http" 44 | if encrypt: 45 | protocol += "s" 46 | return "/".join([ 47 | (protocol + ":/"), 48 | ":".join([host, port]) 49 | ]) 50 | 51 | signal response_recieved( 52 | ## HTTP Status Code 53 | code: int, 54 | headers: Dictionary, 55 | body: PackedByteArray 56 | ) 57 | 58 | ## Sets the port to 443 if encrypt is true, otherwise 80, if it is set to -1. 59 | func _init(): 60 | if port == - 1: 61 | if encrypt: 62 | port = 443 63 | else: 64 | port = 80 65 | 66 | ## Submits form data and returns HTTP status code of the response. 67 | ## Web export is not supported. 68 | func submit(fields: Dictionary): 69 | if OS.has_feature("web"): 70 | push_error("Error: HttpProtocol does not support web export") # because that would require async code 71 | return 72 | 73 | var err = 0 74 | var headers_arr: Array = [] 75 | var response_code = 0 76 | for key in headers: 77 | headers_arr.append(key + ": " + headers[key]) 78 | 79 | err = http.connect_to_host(host, port) 80 | if err != OK: 81 | push_error("Error ", err, " connecting to host ", host, ":", port) 82 | return 83 | 84 | while http.get_status() == HTTPClient.STATUS_CONNECTING||http.get_status() == HTTPClient.STATUS_RESOLVING: 85 | http.poll() 86 | if http.get_status() == HTTPClient.STATUS_CONNECTING: 87 | print("Connecting...") 88 | else: 89 | print("Resolving...") 90 | OS.delay_msec(100) 91 | 92 | if http.get_status() != HTTPClient.STATUS_CONNECTED: 93 | push_error(http_client_status_to_string(http.get_status())) 94 | 95 | err = http.request(int(method), path, headers_arr, JSON.stringify(fields)) 96 | if err != OK: 97 | push_error("Error ", err, " sending request to '", path, "'") 98 | return 99 | 100 | while http.get_status() == HTTPClient.STATUS_CONNECTING: 101 | http.poll() 102 | print("Connecting...") 103 | OS.delay_msec(100) 104 | 105 | while http.get_status() == HTTPClient.STATUS_REQUESTING: 106 | http.poll() 107 | print("Requesting...") 108 | OS.delay_msec(100) 109 | 110 | if http.get_status() != HTTPClient.STATUS_BODY&&http.get_status() != HTTPClient.STATUS_CONNECTED: 111 | push_error(http_client_status_to_string(http.get_status())) 112 | return 113 | 114 | if http.has_response(): 115 | response_code = http.get_response_code() 116 | print("Response Code: ", response_code) 117 | 118 | var data = PackedByteArray() 119 | 120 | while http.get_status() == HTTPClient.STATUS_BODY: 121 | http.poll() 122 | 123 | var chunk = http.read_response_body_chunk() 124 | if chunk.size() == 0: 125 | OS.delay_usec(1000) # wait for buffers to fill 126 | else: 127 | data += chunk 128 | 129 | response_recieved.emit(response_code, http.get_response_headers_as_dictionary(), data) 130 | else: 131 | print("No response") 132 | return response_code 133 | 134 | ## Returns a string representation of the HTTPClient.STATUS_. or "Status: {status}" if the status is not one of the following: 135 | ## 2. Error resolving host {host} 136 | ## 4. Error connecting to host {host}:{port} 137 | ## 8. Error in HTTP connection 138 | ## 9. Error in TLS handshake 139 | func http_client_status_to_string( 140 | ## Status returned by HTTPClient.get_status() 141 | status: int 142 | ) -> String: 143 | var error := "Error {0}" 144 | var msg := "" 145 | match status: 146 | HTTPClient.STATUS_CANT_RESOLVE: 147 | msg = "resolving host " + host 148 | HTTPClient.STATUS_CANT_CONNECT: 149 | msg = "connecting to host " + host + ":" + str(port) 150 | HTTPClient.STATUS_CONNECTION_ERROR: 151 | msg = "in HTTP connection" 152 | HTTPClient.STATUS_TLS_HANDSHAKE_ERROR: 153 | msg = "in TLS handshake" 154 | _: 155 | msg = "Status: " + str(status) 156 | return error.format([msg]) 157 | -------------------------------------------------------------------------------- /addons/form/nodes/Protocols/MailProtocol.gd: -------------------------------------------------------------------------------- 1 | ## Handles form submission and response over the network using E-Mail. 2 | class_name MailProtocol extends NetworkProtocol 3 | 4 | const HTML_SUBSTITUTES := { 5 | "&": "&", # must be first 6 | 7 | "\"": """, 8 | "<": "<", 9 | ">": ">", 10 | "'": "'", 11 | } 12 | 13 | @export_group("Body") 14 | enum BodyFormat { 15 | ## HyperText Markup Language Form with disabled inputs 16 | HTML, 17 | ## {key}: {value} 18 | PLAIN_TEXT, 19 | ## JavaScript Object Notation 20 | JSON 21 | } 22 | @export var body_format: BodyFormat = BodyFormat.HTML 23 | ## Path to CSS file to use for styling regardless of body_format 24 | @export_file var css := "../../styles/default.css" 25 | 26 | @export_group("Metadata") 27 | ## Sender name 28 | @export var from_name: String 29 | ## Sender address 30 | @export var from_address: String 31 | ## Recipient address 32 | @export var to_address: String 33 | ## Subject line 34 | @export var subject: String 35 | 36 | ## Submits form data in an E-Mail to the recipient and returns HTTP status code of the response. 37 | func submit(fields: Dictionary) -> int: 38 | return super.submit(fields) 39 | 40 | ## Generates the body for the E-Mail in the body_format. 41 | func generate_body( 42 | ## Output of Form.generate_fields_dict() to populate body 43 | fields: Dictionary 44 | ) -> String: 45 | var body := "" 46 | var style := "" 47 | var suffix := "" 48 | var format_line := "{key}: {value}\n" 49 | 50 | match body_format: 51 | BodyFormat.HTML: 52 | body = "
" 53 | if FileAccess.file_exists(css): 54 | style = "" 55 | format_line = "<{container_type} disabled type=\"{type}\" {suffix}" 56 | suffix = "
" 57 | BodyFormat.JSON: 58 | body = "{" 59 | format_line = "\"{key}\": {value}," 60 | suffix = "}" 61 | 62 | for field in fields: 63 | var typed_value = super.get_value(fields[field]) 64 | var value = get_value(fields[field]) 65 | 66 | # html specific 67 | var line_suffix := "value = \"{value}\">
".format({"value": value}) 68 | var container_type := "input" 69 | 70 | match body_format: 71 | BodyFormat.JSON: 72 | if typeof(typed_value) == TYPE_STRING||typeof(typed_value) == TYPE_NIL||typeof(typed_value) == TYPE_OBJECT: 73 | value = "\"" + value + "\"" 74 | # remove trailing comma 75 | if field == fields.keys().back(): 76 | format_line = format_line.replace(",", "") 77 | BodyFormat.HTML: 78 | # if the value is a boolean and true 79 | match typeof(typed_value): 80 | TYPE_BOOL: 81 | if typed_value: 82 | # the input will need the checked attribute but no value 83 | line_suffix = "checked>
" 84 | TYPE_ARRAY: 85 | line_suffix = ">" 86 | for item in typed_value: 87 | var checked = "" 88 | if item.selected: # set in Protocol.get_value() 89 | checked = "checked" 90 | var text = item.text 91 | sanitize_for_html(text) 92 | # [x] item 93 | line_suffix += "
  • {name}
  • ".format({ 94 | "value": checked, "name": text 95 | }) 96 | container_type = "ul" 97 | line_suffix += "
    " 98 | TYPE_STRING: 99 | value = sanitize_for_html(value) 100 | body += format_line.format({ 101 | "key": field, "value": value, 102 | # html specific 103 | "type": MailProtocol.type_to_string(typeof(typed_value)), 104 | "suffix": line_suffix, 105 | "container_type": container_type 106 | }) 107 | return body + style + suffix 108 | 109 | ## Converts a type to a string for use in HTML form. 110 | ## TYPE_STRING -> "text" 111 | ## TYPE_BOOL -> "checkbox" 112 | ## TYPE_INT -> "number" 113 | ## TYPE_FLOAT -> "number" 114 | ## TYPE_ARRAY -> "select" 115 | ## TYPE_NIL -> "text" 116 | static func type_to_string( 117 | ## Output of typeof() 118 | type: int 119 | ) -> String: 120 | return { 121 | TYPE_STRING: "text", 122 | TYPE_BOOL: "checkbox", 123 | TYPE_INT: "number", 124 | TYPE_FLOAT: "number", 125 | TYPE_ARRAY: "select", 126 | TYPE_NIL: "text" 127 | }[type] 128 | 129 | ## Returns the value of a node as a string for use in the E-Mail body. 130 | func get_value(subject: Node) -> Variant: 131 | var value = super.get_value(subject) 132 | if subject is ItemList&&body_format == BodyFormat.JSON: 133 | return JSON.stringify(value) 134 | elif subject is ItemList||subject is GraphEdit: 135 | return ", ".join(value) 136 | else: 137 | return str(value) 138 | 139 | ## Sanitizes a string for use in HTML. 140 | func sanitize_for_html(subject: String) -> String: 141 | for char in HTML_SUBSTITUTES.keys(): 142 | subject = subject.replace(char, HTML_SUBSTITUTES[char]) 143 | return subject 144 | -------------------------------------------------------------------------------- /addons/form/nodes/Protocols/MailsendSmtpMailProtocol.gd: -------------------------------------------------------------------------------- 1 | ## Handles form submission and response over the network using mailsend. 2 | ## Mailsend must be installed. 3 | class_name MailsendSmtpMailProtocol extends SmtpMailProtocol 4 | 5 | @export var mailsend_executable_path = "mailsend-go" 6 | 7 | @export_group("Mailsend Arguments") 8 | ## Print debug messages 9 | @export var debug: bool = false 10 | ## Verify Certificate in connection 11 | @export var verifyCert: bool = false 12 | ## Write log messages to this file 13 | @export_global_file var log: String = "" 14 | 15 | # Calls mailsend with the given body and parameters based on the properties and returns the status code. 16 | func handle_smtp( 17 | ## E-mail body 18 | body: String 19 | ) -> int: 20 | var output := [] 21 | var args := [ 22 | "-smtp", host, "-port", port, 23 | "auth", 24 | "-user", username, 25 | "-pass", password, 26 | 27 | "-sub", subject, 28 | "-fname", from_name, 29 | "-f", from_address, 30 | "-t", to_address, 31 | 32 | "body", "-msg", body 33 | ] 34 | 35 | if debug: 36 | args.append("-debug") 37 | if verifyCert: 38 | args.append("-verifyCert") 39 | if log != "": 40 | args.append_array(["-log", log]) 41 | 42 | sanitize_shell_args(args) 43 | print("Running ", " ", mailsend_executable_path, " \"", "\" \"".join(args), "\"") 44 | var code = OS.execute(mailsend_executable_path, args, output, true) 45 | for line in output[0].split("\n"): 46 | print("mailsend: ", line) 47 | return code 48 | 49 | ## Sanitize subject for use as shell command args 50 | func sanitize_shell_args( 51 | ## Shell Command Args (passed by reference) 52 | subject: Array[String], 53 | ## Stores every instance of every character that was caught by the sanitization in order of appearance (passed by reference) 54 | jail: Array=[] 55 | ): 56 | return subject.map(func(arg): 57 | var sanitized=sanitize(arg, jail, sanitization, true) 58 | if sanitization != Sanitization.SHELL_ESCAPE&&sanitization != Sanitization.SHELL_BLACKLIST: 59 | sanitized=sanitize(sanitized, jail, Sanitization.SHELL_ESCAPE, true) 60 | return sanitized 61 | ) 62 | -------------------------------------------------------------------------------- /addons/form/nodes/Protocols/NetworkProtocol.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | ## Handles form submission and response over the network. 3 | class_name NetworkProtocol extends Protocol 4 | 5 | @export_group("Target") 6 | ## Target hostname 7 | @export var host := "localhost" 8 | ## Target port 9 | ## -1 means "use default port" 10 | @export_range(-1, 65536) var port := - 1: 11 | set(new_val): 12 | if new_val >= 0: 13 | new_val = new_val % 65536 14 | elif new_val < - 1: 15 | new_val = 0 16 | port = new_val 17 | 18 | ## Use authentication for target 19 | @export var use_host_authentication := false 20 | ## Username at target 21 | @export var host_username := "": 22 | set(new_val): 23 | use_host_authentication = new_val != "" 24 | host_username = new_val 25 | ## Password at target 26 | @export var host_password := "" 27 | ## Path to private key file 28 | @export_file var host_keyfile := "" 29 | 30 | ## Tries to set host_username based on environment var, if not already set 31 | func _init() -> void: 32 | if OS.has_environment("USERNAME"): 33 | host_username = OS.get_environment("USERNAME") -------------------------------------------------------------------------------- /addons/form/nodes/Protocols/SmtpMailProtocol.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | ## Handles form submission and response over the network using Simple Mail Transfer Protocol (SMTP). 3 | class_name SmtpMailProtocol extends MailProtocol 4 | 5 | enum SecurityType { 6 | NONE = 0, 7 | SSL = 465, 8 | TLS = 587 9 | } 10 | ## Security Type 11 | ## Adjusts the port if it is 0, 465 or 587. 12 | @export var security_type: SecurityType = SecurityType.SSL: 13 | set(new_val): 14 | security_type = new_val 15 | if port in SecurityType.values(): 16 | port = new_val # TODO: this does not work and I have no idea why 17 | 18 | @export_group("Authentication") 19 | ## Authenticate with the mail server 20 | @export var use_authentication := true 21 | ## Username at the mail server 22 | @export var username: String 23 | ## Password at the mail server 24 | @export var password: String 25 | 26 | ## Sets the port based on the security type if it is -1. 27 | func _init() -> void: 28 | if port == - 1: 29 | port = security_type 30 | 31 | ## Returns handle_smtp(generate_body(fields)). 32 | func submit( 33 | ## Output of Form.generate_fields_dict() to populate body 34 | fields: Dictionary 35 | ) -> int: 36 | return handle_smtp(generate_body(fields)) 37 | 38 | ## Handles the SMTP request and returns the status code. 39 | func handle_smtp( 40 | ##E-Mail body 41 | body: String 42 | ) -> int: 43 | push_error("not implemented") 44 | return - 1 45 | -------------------------------------------------------------------------------- /addons/form/nodes/Submit.gd: -------------------------------------------------------------------------------- 1 | ## Button that submits the form data. 2 | ## This class only exists to avoid having to rename a button and to have an icon in the editor 3 | class_name Submit extends Button 4 | -------------------------------------------------------------------------------- /addons/form/nodes/ValidatableLineEdit.gd: -------------------------------------------------------------------------------- 1 | ## LineEdit with a validator. 2 | class_name ValidatableLineEdit extends LineEdit 3 | 4 | @export var validator: Validator 5 | 6 | ## Assigns the style from the validator if it exists. 7 | func _init(): 8 | if validator: 9 | validator.style_valid = get_theme_stylebox("normal") 10 | -------------------------------------------------------------------------------- /addons/form/nodes/ValidatableTextEdit.gd: -------------------------------------------------------------------------------- 1 | ## TextEdit with a validator. 2 | class_name ValidatableTextEdit extends TextEdit 3 | 4 | @export var validator: Validator 5 | 6 | ## Assigns the style from the validator if it exists. 7 | func _init(): 8 | if validator: 9 | validator.style_valid = get_theme_stylebox("normal") 10 | -------------------------------------------------------------------------------- /addons/form/nodes/Validator.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | ## Validates Text Input according to rules about length and content 3 | class_name Validator extends Resource 4 | 5 | ## A collection of predefined regular expression patterns. 6 | ## Items correspond to the PredefinedRegEx enum. 7 | const REGEX_LIB = [ 8 | "^[a-zA-Z]+$", 9 | "^[0-9]+$", 10 | "^[a-zA-Z0-9]+$", 11 | "^(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){255,})(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){65,}@)(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22))(?:\\.(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\\]))$", 12 | # source: emailregex.com 13 | "^[\\+]?[(]?[0-9]{3}[)]?[-\\s\\.]?[0-9]{3}[-\\s\\.]?[0-9]{4,6}$" 14 | # source: ihateregex.io/expr/phone 15 | ] 16 | 17 | ## A list of redundant regex patterns that don't need to be checked. 18 | ## This is used to avoid unnecessary checks when the custom regex is empty. 19 | const REDUNDANT_REGEX = ["", null, ".*"] 20 | 21 | 22 | @export_group("Rules") 23 | 24 | @export_subgroup("Simple Rules") 25 | ## "Input must have a value" 26 | ## min_length will be adjusted if needed 27 | @export var required := false: 28 | set(new_val): 29 | if new_val: 30 | if min_length < 1: 31 | _prev_min_length = min_length 32 | min_length = 1 33 | else: 34 | min_length = _prev_min_length 35 | required = new_val 36 | 37 | ## temporary storage of min_length used by the setter of required 38 | var _prev_min_length := 0 39 | ## Minimum number of characters 40 | @export var min_length := 0: 41 | set(new_val): 42 | if new_val < 0: 43 | new_val = 0 44 | if new_val == 0&&required: 45 | required = false 46 | min_length = new_val 47 | ## Minimum and Maximum number of matches for \w+ allowed 48 | @export var word_range: Boundaries 49 | ## List of allowed strings 50 | @export var whitelist: ListFilter 51 | ## List of prohibited strings 52 | @export var blacklist: ListFilter 53 | 54 | @export_subgroup("Regular Expression") 55 | enum PredefinedRegEx {NONE = -1, 56 | ALPHABETICAL, NUMERICAL, ALPHANUMERICAL, EMAIL_ADDRESS, PHONE_NUMBER 57 | } 58 | ## Predefined pattern to match against 59 | @export var predefined: PredefinedRegEx = -1 60 | 61 | enum Behaviour { 62 | ## Input must match both the predefined and the custom regex (if both are set) 63 | MUST_MATCH_BOTH = 0, 64 | ## Input can match either the predefined or the custom regex (if at least one is set) 65 | CAN_MATCH_EITHER = 1 66 | } 67 | ## How predefined and custom regexes are checked against in relation to each other. 68 | @export var behaviour: Behaviour 69 | 70 | ## Custom Pattern to match against 71 | @export var custom := ".*" 72 | ## Normalise the case before matching 73 | @export var normalise := false 74 | ## Remove these strings from the subject before matching 75 | @export var remove: Array[String] 76 | ## Don't allow any more than one match 77 | @export var require_single_match := false 78 | 79 | ## Validation passed (updated on text change) 80 | var valid := false 81 | ## Broken Rules with names and relevant value 82 | var broken_rules := {} 83 | ## Compiled custom regex 84 | var user_regex: RegEx = RegEx.new() 85 | 86 | ## Validates given text and updates valid property 87 | func _on_text_changed( 88 | ## New content of the input 89 | new_text: String 90 | ) -> void: 91 | valid = validate(new_text) 92 | 93 | ## Validates given text against all rules and returns validity 94 | func validate( 95 | ## Text to validate 96 | subject: String 97 | ) -> bool: 98 | broken_rules = {} 99 | var _regex := RegEx.new() 100 | 101 | ##-- min_length --## 102 | var length = subject.length() 103 | if length < min_length: 104 | broken_rules["min_length"] = str(length) 105 | return false 106 | 107 | ##-- filter_list --## 108 | if ( 109 | blacklist != null&&blacklist.size() > 0 110 | &&blacklist.is_represented_in(subject) 111 | ): 112 | broken_rules["filter_list"] = "blacklist" 113 | return false 114 | 115 | if ( 116 | whitelist != null&&whitelist.size() > 0 117 | &&!whitelist.is_represented_in(subject) 118 | ): 119 | broken_rules["filter_list"] = "whitelist" 120 | return false 121 | 122 | ##-- word_range --## 123 | if word_range != null&&(word_range.min != 0||word_range.max != 0): 124 | _regex.compile("\\w+") # word 125 | var matches = _regex.search_all(subject) 126 | if matches == null: 127 | if word_range.min: 128 | broken_rules["word_range"] = "min" 129 | return false 130 | matches = [] # size() = 0 131 | var count = matches.size() 132 | if count > word_range.max: 133 | broken_rules["word_range"] = "max" 134 | return false 135 | elif count < word_range.min: 136 | broken_rules["word_range"] = "min" 137 | return false 138 | 139 | ##-- predefined_regex --## 140 | var predefined_regex_result := false 141 | 142 | if normalise: 143 | subject = subject.to_lower() 144 | if remove.size() > 0: 145 | for item in remove: 146 | subject = subject.replace(item, "") 147 | 148 | if predefined != PredefinedRegEx.NONE: 149 | _regex.compile(REGEX_LIB[predefined]) 150 | if _regex.search(subject) != null: # if there is a match 151 | predefined_regex_result = true 152 | if behaviour == Behaviour.CAN_MATCH_EITHER: # if we only need one match 153 | return true # return true, since this is the last check before user_regex, which we don't need to run, since we only need one match 154 | elif behaviour == Behaviour.MUST_MATCH_BOTH: # if no match is found and we need both 155 | broken_rules["predefined"] = Behaviour.keys()[behaviour] 156 | return false 157 | # else we need both and there is no match, so we keep predefined_regex_result as false and continue to user_regex 158 | else: # if there is no predefined regex, we don't need to run it 159 | predefined_regex_result = true 160 | 161 | ##-- user_regex --## 162 | if user_regex.compile(custom) == OK and user_regex.is_valid() \ 163 | and user_regex.get_pattern() not in REDUNDANT_REGEX: 164 | if require_single_match: 165 | var matches = user_regex.search_all(subject) 166 | if matches != null&&matches.size() == 1&&matches[0].get_string() == subject.strip_edges(): 167 | return true 168 | return predefined_regex_result||bool(behaviour) 169 | 170 | elif user_regex.search(subject) != null: 171 | return true 172 | 173 | broken_rules["custom"] = custom 174 | return false 175 | elif user_regex.get_pattern() in REDUNDANT_REGEX: 176 | return true 177 | else: 178 | push_warning('Incorrect custom RegEx pattern was set to: ', resource_path) 179 | 180 | return false # Always returns false if not able to validate in any way. -------------------------------------------------------------------------------- /addons/form/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="GodotForm" 4 | description="All the tools you need for serving and validating forms." 5 | author="Moritz Tim Widmann @ TWEAKLAB" 6 | version="4.0.4-beta" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/form/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | var _types: Array[String] 5 | 6 | func _enter_tree(): 7 | add_custom_types([ 8 | ["Form", "Control", preload ("nodes/Form.gd"), preload ("icons/Form.svg")], 9 | ["Submit", "Button", preload ("nodes/Submit.gd"), preload ("icons/Submit.svg")], 10 | ["ValidatableLineEdit", "LineEdit", preload ("nodes/ValidatableLineEdit.gd"), preload ("icons/ValidatableLineEdit.svg")], 11 | ["ValidatableTextEdit", "TextEdit", preload ("nodes/ValidatableTextEdit.gd"), preload ("icons/ValidatableTextEdit.svg")], 12 | ["FormLabel", "Label", preload ("nodes/FormLabel.gd"), preload ("icons/FormLabel.svg")], 13 | ["Validator", "Resource", preload ("nodes/Validator.gd"), preload ("icons/Validator.svg")], 14 | ["Boundaries", "Resource", preload ("nodes/Boundaries.gd"), preload ("icons/Boundaries.svg")], 15 | ["ListFilter", "Resource", preload ("nodes/ListFilter.gd"), preload ("icons/ListFilter.svg")], 16 | ["Protocol", "Resource", preload ("nodes/Protocol.gd"), preload ("icons/Protocol.svg")], 17 | ["FileProtocol", "Resource", preload ("res://addons/form/nodes/Protocols/FileProtocol.gd"), preload ("icons/FileProtocol.svg")], 18 | ["NetworkProtocol", "Resource", preload ("nodes/Protocols/NetworkProtocol.gd"), preload ("icons/NetworkProtocol.svg")], 19 | ["HttpProtocol", "Resource", preload ("nodes/Protocols/HttpProtocol.gd"), preload ("icons/HttpProtocol.svg")], 20 | ["MailProtocol", "Resource", preload ("nodes/Protocols/MailProtocol.gd"), preload ("icons/MailProtocol.svg")], 21 | ["SmtpMailProtocol", "Resource", preload ("nodes/Protocols/SmtpMailProtocol.gd"), preload ("icons/SmtpMailProtocol.svg")], 22 | ["MailsendSmtpMailProtocol", "Resource", preload ("nodes/Protocols/MailsendSmtpMailProtocol.gd"), preload ("icons/MailsendSmtpMailProtocol.svg")] 23 | ]) 24 | 25 | func add_custom_types(types: Array): 26 | for type in types: 27 | _types.append(type[0]) 28 | add_custom_type(type[0], type[1], type[2], type[3]) 29 | 30 | func _exit_tree(): 31 | for _type in _types: 32 | remove_custom_type(_type) 33 | -------------------------------------------------------------------------------- /addons/form/readme images/Download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/addons/form/readme images/Download.png -------------------------------------------------------------------------------- /addons/form/readme images/Download.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b51m6ouxaan7o" 6 | path="res://.godot/imported/Download.png-db51d3c77d2750215e1480b5103154bd.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/readme images/Download.png" 14 | dest_files=["res://.godot/imported/Download.png-db51d3c77d2750215e1480b5103154bd.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/form/readme images/Editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/addons/form/readme images/Editor.png -------------------------------------------------------------------------------- /addons/form/readme images/Editor.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://xn3chlg0gjfn" 6 | path="res://.godot/imported/Editor.png-1ab08715cb154e8f9b4573652add73ce.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/readme images/Editor.png" 14 | dest_files=["res://.godot/imported/Editor.png-1ab08715cb154e8f9b4573652add73ce.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/form/readme images/Enable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/addons/form/readme images/Enable.png -------------------------------------------------------------------------------- /addons/form/readme images/Enable.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d0nmc3dlycvjl" 6 | path="res://.godot/imported/Enable.png-d75c796fb8de0e0d4033964ea297fb48.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/readme images/Enable.png" 14 | dest_files=["res://.godot/imported/Enable.png-d75c796fb8de0e0d4033964ea297fb48.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/form/readme images/Game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/addons/form/readme images/Game.png -------------------------------------------------------------------------------- /addons/form/readme images/Game.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cbwugtxqbgkiy" 6 | path="res://.godot/imported/Game.png-572c93d4d63a208d7b32fda39c20d943.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/readme images/Game.png" 14 | dest_files=["res://.godot/imported/Game.png-572c93d4d63a208d7b32fda39c20d943.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /addons/form/readme images/Game_400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/addons/form/readme images/Game_400.png -------------------------------------------------------------------------------- /addons/form/readme images/Game_400.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bxobpox3nxys2" 6 | path="res://.godot/imported/Game_400.png-cb35354c56d8407653fbe12b6ceac14c.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://addons/form/readme images/Game_400.png" 14 | dest_files=["res://.godot/imported/Game_400.png-cb35354c56d8407653fbe12b6ceac14c.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/icon.png -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/.gitkeep -------------------------------------------------------------------------------- /test/Test.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://6cxcjnw6rt30"] 2 | 3 | [ext_resource type="Script" path="res://test/nodes/Protocols/MailsendSmtpMailProtocol.test.gd" id="1_ef4ig"] 4 | 5 | [node name="Node" type="Node"] 6 | script = ExtResource("1_ef4ig") 7 | 8 | [connection signal="ready" from="." to="." method="print_report"] 9 | -------------------------------------------------------------------------------- /test/nodes/Boundaries.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/Boundaries.test.gd -------------------------------------------------------------------------------- /test/nodes/Form.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/Form.test.gd -------------------------------------------------------------------------------- /test/nodes/FormLabel.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/FormLabel.test.gd -------------------------------------------------------------------------------- /test/nodes/ListFilter.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/ListFilter.test.gd -------------------------------------------------------------------------------- /test/nodes/Protocol.test.gd: -------------------------------------------------------------------------------- 1 | class_name ProtocolTest extends ClassTest 2 | 3 | func run(): 4 | instance = Protocol.new() 5 | test_sanitize( 6 | "No sanitization", 7 | "Hello World!", 8 | "Hello World!", 9 | Protocol.Sanitization.NONE 10 | ) 11 | test_sanitize( 12 | "Alphanumeric", 13 | "H3ll0!", 14 | "H3ll0", 15 | Protocol.Sanitization.ALPHANUMERIC 16 | ) 17 | test_sanitize( 18 | "Shell Escape", 19 | "\"Hello World!\\\"", 20 | "\\\"Hello World\\!\\\\\\\"", 21 | Protocol.Sanitization.SHELL_ESCAPE 22 | ) 23 | test_sanitize( 24 | "Shell Blacklist", 25 | "\"Hello World!\\\"", 26 | "Hello World", 27 | Protocol.Sanitization.SHELL_BLACKLIST 28 | ) 29 | test_sanitize( 30 | "Shell Whitelist", 31 | "Hello World?", 32 | "Hello World", 33 | Protocol.Sanitization.SHELL_WHITELIST 34 | ) 35 | test_sanitize( 36 | "Custom Blacklist", 37 | "Hello!", 38 | "Hell", 39 | Protocol.Sanitization.NONE, 40 | Protocol.Sanitization.NONE, 41 | false, 42 | "!o" 43 | ) 44 | test_sanitize( 45 | "Sanitize Keys", 46 | { 47 | "Hello World!": "Hello World!" 48 | }, 49 | { 50 | "Hello World": "Hello World" 51 | }, 52 | Protocol.Sanitization.NONE, 53 | Protocol.Sanitization.NONE, 54 | true, 55 | "!" 56 | ) 57 | 58 | 59 | 60 | func test_sanitize( 61 | test_name, 62 | ## Any Variant that needs to be sanitized. 63 | ## Dictionaries and Arrays are sanitized recursively. 64 | ## Any other type will be returned as is. 65 | subject: Variant, 66 | ## Expected value 67 | expected: Variant, 68 | ## How User Input is sanitized when the protocol deems it necessary. 69 | sanitization: Protocol.Sanitization, 70 | ## The sanitization method to use. 71 | sanitization_override:Protocol.Sanitization = sanitization, 72 | ## Whether to sanitize Dictionary keys. Note that this is passed down recursively. 73 | sanitize_keys := false, 74 | ## Additional characters to remove from user input before sanitization. 75 | ## Example: "@#$%&!" 76 | blacklist: String = "" 77 | ): 78 | var jail = [] 79 | instance.sanitization = sanitization 80 | instance.blacklist = blacklist 81 | var actual = instance.sanitize(subject, jail, sanitization_override, sanitize_keys) 82 | compare(actual, expected, "sanitize() with " + test_name) 83 | print("caught:\n", jail) 84 | -------------------------------------------------------------------------------- /test/nodes/Protocols/FileProtocol.test.gd: -------------------------------------------------------------------------------- 1 | class_name FileProtocolTest extends ClassTest 2 | 3 | func run(): 4 | instance = FileProtocol.new() 5 | 6 | instance.file_name_scheme = '{hash}-{id}-{year}-{month}-{day}-{weekday}-{hour}-{minute}-{second}.json' 7 | 8 | # create temporary directory 9 | instance.target_dir = '/tmp/godot-FileProtocol-test' 10 | DirAccess.make_dir_absolute(instance.target_dir) 11 | var i = 1 12 | while (!DirAccess.dir_exists_absolute(instance.target_dir)): 13 | instance.target_dir = '/tmp/godot-FileProtocol-test' + i 14 | DirAccess.make_dir_absolute(instance.target_dir) 15 | i += 1 16 | 17 | var format_dict = { 18 | hash = {}.hash(), 19 | id = 1 20 | } 21 | format_dict.merge(Time.get_date_dict_from_system()) 22 | format_dict.merge(Time.get_time_dict_from_system()) 23 | compare( 24 | instance.generate_file_name({}), 25 | instance.file_name_scheme.format(format_dict), 26 | 'generate file name' 27 | ) 28 | -------------------------------------------------------------------------------- /test/nodes/Protocols/HttpProtocol.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/Protocols/HttpProtocol.test.gd -------------------------------------------------------------------------------- /test/nodes/Protocols/MailProtocol.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/Protocols/MailProtocol.test.gd -------------------------------------------------------------------------------- /test/nodes/Protocols/MailsendSmtpMailProtocol.test.gd: -------------------------------------------------------------------------------- 1 | class_name MailsendSmtpMailProtocolTest extends ClassTest 2 | 3 | func run(): 4 | var instance = MailsendSmtpMailProtocol.new() # can't use class var. godot#86963 5 | instance.sanitization = MailsendSmtpMailProtocol.Sanitization.SHELL_ESCAPE 6 | var jail = [] 7 | var actual = instance.sanitize_shell_args([ 8 | MailsendSmtpMailProtocol.SHELL_BLACKLIST 9 | ], jail) 10 | compare( 11 | actual, 12 | ["\\\"\\$\\%\\`\\!"], 13 | "sanitize shell args with SHELL_ESCAPE" 14 | ) 15 | print("caught:\n", jail) 16 | instance.sanitization = MailsendSmtpMailProtocol.Sanitization.SHELL_WHITELIST 17 | jail = [] 18 | actual = instance.sanitize_shell_args([ 19 | MailsendSmtpMailProtocol.SHELL_WHITELIST, 20 | MailsendSmtpMailProtocol.SHELL_BLACKLIST + "}", 21 | MailsendSmtpMailProtocol.SHELL_WHITELIST + "}" + MailsendSmtpMailProtocol.SHELL_BLACKLIST 22 | ], jail) 23 | compare( 24 | actual, 25 | [MailsendSmtpMailProtocol.SHELL_WHITELIST, "", MailsendSmtpMailProtocol.SHELL_WHITELIST], 26 | "sanitize shell args with SHELL_WHITELIST" 27 | ) 28 | print("caught:\n", jail) 29 | instance.sanitization = MailsendSmtpMailProtocol.Sanitization.SHELL_BLACKLIST 30 | jail = [] 31 | actual = instance.sanitize_shell_args([ 32 | MailsendSmtpMailProtocol.SHELL_WHITELIST, 33 | MailsendSmtpMailProtocol.SHELL_BLACKLIST + "}", 34 | MailsendSmtpMailProtocol.SHELL_WHITELIST + "}" + MailsendSmtpMailProtocol.SHELL_BLACKLIST 35 | ], jail) 36 | compare( 37 | actual, 38 | [ 39 | MailsendSmtpMailProtocol.SHELL_WHITELIST, 40 | "}", 41 | MailsendSmtpMailProtocol.SHELL_WHITELIST + "}" 42 | ], 43 | "sanitize shell args with SHELL_BLACKLIST" 44 | ) 45 | print("caught:\n", jail) 46 | -------------------------------------------------------------------------------- /test/nodes/Protocols/NetworkProtocol.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/Protocols/NetworkProtocol.test.gd -------------------------------------------------------------------------------- /test/nodes/Protocols/SmtpMailProtocol.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/Protocols/SmtpMailProtocol.test.gd -------------------------------------------------------------------------------- /test/nodes/Submit.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/Submit.test.gd -------------------------------------------------------------------------------- /test/nodes/ValidatableLineEdit.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/ValidatableLineEdit.test.gd -------------------------------------------------------------------------------- /test/nodes/ValidatableTextEdit.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/ValidatableTextEdit.test.gd -------------------------------------------------------------------------------- /test/nodes/Validator.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/nodes/Validator.test.gd -------------------------------------------------------------------------------- /test/plugin.test.gd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritztim/Godot-Form/010f23b303e56443f1b51591780594ff4cb670ce/test/plugin.test.gd -------------------------------------------------------------------------------- /test/test.gd: -------------------------------------------------------------------------------- 1 | #gdscript 2 | class_name ClassTest extends Node 3 | 4 | ## Dictionary of test names and results 5 | var results = {} 6 | 7 | ## Instance to run tests on 8 | var instance 9 | 10 | ## Print test results 11 | @export var verbose = true 12 | 13 | ## Run all tests and update results 14 | func run(): 15 | return {} 16 | 17 | ## Call run() and print results 18 | func print_report(): 19 | run() 20 | if results.size() == 0: 21 | print("❔ No tests ran") 22 | return 23 | print( 24 | "✅" if results.values().all(func(x): return x) else "❌", 25 | " ", results.values().count(true), "/", results.size(), " tests passed." 26 | ) 27 | 28 | func compare( 29 | ## Actual value 30 | actual, 31 | ## Expected value 32 | expected, 33 | ## Name of test 34 | test_name 35 | ): 36 | if verbose: print("\n", "------", results.size()+1, ": ", test_name, "------") 37 | results[test_name] = actual == expected 38 | if !verbose: return 39 | print( 40 | "✅ passed" if results[test_name] else "❌ failed" 41 | ) 42 | if results[test_name]: return 43 | print( 44 | "\n🟢 expected: \n", expected, 45 | "\n🔴 actual: \n", actual 46 | ) 47 | --------------------------------------------------------------------------------