├── .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 |
3 | Godot Form Plugin
4 |
5 |
6 | ## Example
7 | ### In the Editor
8 | This is how a form might look in the editor:
9 | 
10 | ### In Action
11 | This is how the form might look in the game:
12 | 
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 | 
24 | 2. Enable the plugin in the Project Settings:
25 | 
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 |
3 | Godot Form Plugin
4 |
5 |
6 | ## Example
7 | ### In the Editor
8 | This is how a form might look in the editor:
9 | 
10 | ### In Action
11 | This is how the form might look in the game:
12 | 
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 | 
24 | 2. Enable the plugin in the Project Settings:
25 | 
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 = ""
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 |
--------------------------------------------------------------------------------