diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..a7e4f2b3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 deadpixelsociety + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/godot-form-validator/control_validator.gd b/addons/godot-form-validator/control_validator.gd new file mode 100644 index 00000000..28f35d94 --- /dev/null +++ b/addons/godot-form-validator/control_validator.gd @@ -0,0 +1,62 @@ +@tool +extends Node +class_name ControlValidator + +@export var validator: Validator: + set(value): + validator = value + _on_validator_added() + + +func _ready() -> void: + tree_entered.connect(_on_tree_entered) + tree_exiting.connect(_on_tree_exiting) + _configure_validator() + _on_validator_added() + + +func _configure_validator() -> void: + if validator != null: + return + var parent = get_parent() + if not parent: + return + var found = Validation.find_validator(parent) + if found: + validator = found.duplicate(true) + else: + validator = Validator.new() + + +func _get_configuration_warnings() -> PackedStringArray: + if not validator: + return [] + var list = PackedStringArray() + for rule in validator.rules: + if not rule.is_valid(): + list.append(rule.get_invalid_message()) + return list + + +func _on_tree_entered() -> void: + _configure_validator() + _on_validator_added() + + +func _on_tree_exiting() -> void: + _on_validator_removed() + + +func _on_validator_added() -> void: + var parent = get_parent() + if not parent or not validator: + return + Validation.validator_added.emit(parent, validator) + update_configuration_warnings() + + +func _on_validator_removed() -> void: + var parent = get_parent() + if not parent: + return + Validation.validator_removed.emit(parent) diff --git a/addons/godot-form-validator/editor_icon.png b/addons/godot-form-validator/editor_icon.png new file mode 100644 index 00000000..b3f822f8 Binary files /dev/null and b/addons/godot-form-validator/editor_icon.png differ diff --git a/addons/godot-form-validator/editor_icon.png.import b/addons/godot-form-validator/editor_icon.png.import new file mode 100644 index 00000000..abf2a09d --- /dev/null +++ b/addons/godot-form-validator/editor_icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://hohynkx70xob" +path="res://.godot/imported/editor_icon.png-6f130a2788dfaa185bebbdd85ed98d66.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/godot-form-validator/editor_icon.png" +dest_files=["res://.godot/imported/editor_icon.png-6f130a2788dfaa185bebbdd85ed98d66.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/addons/godot-form-validator/example/example.gd b/addons/godot-form-validator/example/example.gd new file mode 100644 index 00000000..04a1a373 --- /dev/null +++ b/addons/godot-form-validator/example/example.gd @@ -0,0 +1,34 @@ +extends PanelContainer + +@onready var form_validator_control: FormValidator = $MarginContainer/FormValidator + +func _on_form_validator_control_control_validated(control, passed, messages) -> void: + var error_label = _get_error_label(control) + var valid_label = _get_valid_label(control) + if passed: + if error_label: + error_label.hide() + if valid_label: + valid_label.show() + else: + if error_label: + error_label.text = ", ".join(messages) + error_label.show() + if valid_label: + valid_label.hide() + + +func _get_error_label(control: Control) -> Label: + return find_child(control.name + "Error", true, false) as Label + + +func _get_valid_label(control: Control) -> Label: + return find_child(control.name + "Valid", true, false) as Label + + +func _on_validate_pressed() -> void: + form_validator_control.validate() + + +func _on_exit_pressed() -> void: + get_tree().quit() diff --git a/addons/godot-form-validator/example/example.tscn b/addons/godot-form-validator/example/example.tscn new file mode 100644 index 00000000..e053c21a --- /dev/null +++ b/addons/godot-form-validator/example/example.tscn @@ -0,0 +1,441 @@ +[gd_scene load_steps=41 format=3 uid="uid://dpi5vv8r80o7r"] + +[ext_resource type="Script" path="res://addons/godot-form-validator/example/example.gd" id="1_8caq6"] +[ext_resource type="Script" path="res://addons/godot-form-validator/form_validator.gd" id="2_mtxo0"] +[ext_resource type="Script" path="res://addons/godot-form-validator/control_validator.gd" id="3_hkwup"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/alpha_rule.gd" id="4_dq1df"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/required_rule.gd" id="5_cu56g"] +[ext_resource type="Script" path="res://addons/godot-form-validator/validators/line_edit_validator.gd" id="5_rknpr"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/numeric_rule.gd" id="7_6vtuw"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/alphanumeric_rule.gd" id="8_mu0ia"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/email_rule.gd" id="9_4ynyw"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/length_rule.gd" id="10_iuueu"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/greater_than_rule.gd" id="11_1tya3"] +[ext_resource type="Script" path="res://addons/godot-form-validator/validators/range_validator.gd" id="11_51cp7"] +[ext_resource type="Script" path="res://addons/godot-form-validator/validators/button_validator.gd" id="13_eri47"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/custom_rule.gd" id="13_ibg52"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/boolean_rule.gd" id="15_xjpko"] + +[sub_resource type="LabelSettings" id="LabelSettings_nw8ds"] +font_size = 24 + +[sub_resource type="Resource" id="Resource_i25hd"] +script = ExtResource("5_cu56g") +fail_message = "A value is required." + +[sub_resource type="Resource" id="Resource_4mimn"] +script = ExtResource("4_dq1df") +fail_message = "Value must contain only alpha characters." + +[sub_resource type="Resource" id="Resource_15man"] +script = ExtResource("5_rknpr") +validation_order = 1 +validation_method = 0 +skip_validation = false +rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_i25hd"), SubResource("Resource_4mimn")]) + +[sub_resource type="LabelSettings" id="LabelSettings_su7t3"] +font_color = Color(1, 0.568627, 0.580392, 1) + +[sub_resource type="LabelSettings" id="LabelSettings_urp4f"] +font_color = Color(0.254902, 0.831373, 0.556863, 1) + +[sub_resource type="Resource" id="Resource_j4pud"] +script = ExtResource("5_cu56g") +fail_message = "A value is required." + +[sub_resource type="Resource" id="Resource_xxhfq"] +script = ExtResource("7_6vtuw") +fail_message = "Value must contain only numbers." + +[sub_resource type="Resource" id="Resource_1t3q0"] +script = ExtResource("5_rknpr") +validation_order = 1 +validation_method = 0 +skip_validation = false +rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_j4pud"), SubResource("Resource_xxhfq")]) + +[sub_resource type="Resource" id="Resource_ftvbf"] +script = ExtResource("8_mu0ia") +fail_message = "Value must contain only alphanumeric cahracters." + +[sub_resource type="Resource" id="Resource_my5ss"] +script = ExtResource("5_cu56g") +fail_message = "A value is required." + +[sub_resource type="Resource" id="Resource_61y5a"] +script = ExtResource("5_rknpr") +validation_order = 1 +validation_method = 0 +skip_validation = false +rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_ftvbf"), SubResource("Resource_my5ss")]) + +[sub_resource type="Resource" id="Resource_f3i8j"] +script = ExtResource("9_4ynyw") +fail_message = "Value must be a valid email address." + +[sub_resource type="Resource" id="Resource_l5e3w"] +script = ExtResource("5_cu56g") +fail_message = "A value is required." + +[sub_resource type="Resource" id="Resource_q4oax"] +script = ExtResource("5_rknpr") +validation_order = 1 +validation_method = 0 +skip_validation = false +rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_f3i8j"), SubResource("Resource_l5e3w")]) + +[sub_resource type="Resource" id="Resource_d1g5r"] +script = ExtResource("5_cu56g") +fail_message = "A value is required." + +[sub_resource type="Resource" id="Resource_jqdfw"] +script = ExtResource("10_iuueu") +min_length = 5 +max_length = 10 +fail_message = "" + +[sub_resource type="Resource" id="Resource_62odm"] +script = ExtResource("5_rknpr") +validation_order = 1 +validation_method = 0 +skip_validation = false +rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_d1g5r"), SubResource("Resource_jqdfw")]) + +[sub_resource type="Resource" id="Resource_qm2iv"] +script = ExtResource("5_cu56g") +fail_message = "A value is required." + +[sub_resource type="Resource" id="Resource_brbah"] +script = ExtResource("11_1tya3") +target_value = "3" +fail_message = "" + +[sub_resource type="Resource" id="Resource_smogv"] +script = ExtResource("11_51cp7") +validation_order = 1 +validation_method = 0 +skip_validation = false +rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_qm2iv"), SubResource("Resource_brbah")]) + +[sub_resource type="Resource" id="Resource_4riv3"] +script = ExtResource("13_ibg52") +expression = "control.color.r > 0.5" +fail_message = "The red channel must be greater than 0.5." + +[sub_resource type="Resource" id="Resource_mh7td"] +script = ExtResource("13_eri47") +validation_order = 1 +validation_method = 0 +skip_validation = false +rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_4riv3")]) + +[sub_resource type="Resource" id="Resource_3nuq2"] +script = ExtResource("15_xjpko") +target_value = true +fail_message = "The check box must be checked." + +[sub_resource type="Resource" id="Resource_kbsj8"] +script = ExtResource("13_eri47") +validation_order = -1 +validation_method = 0 +skip_validation = false +rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_3nuq2")]) + +[node name="Example" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_8caq6") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 32 +theme_override_constants/margin_top = 32 +theme_override_constants/margin_right = 32 +theme_override_constants/margin_bottom = 32 + +[node name="FormValidator" type="Control" parent="MarginContainer"] +layout_mode = 2 +script = ExtResource("2_mtxo0") +auto_validate = true + +[node name="VBoxContainer2" type="VBoxContainer" parent="MarginContainer/FormValidator"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Title" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2"] +layout_mode = 2 +text = "Godot Form Validator Examples" +label_settings = SubResource("LabelSettings_nw8ds") +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="GridContainer" type="GridContainer" parent="MarginContainer/FormValidator/VBoxContainer2"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/h_separation = 16 +theme_override_constants/v_separation = 16 +columns = 4 + +[node name="Label" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Alpha" +vertical_alignment = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Alpha" type="LineEdit" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer"] +layout_mode = 2 + +[node name="ControlValidator" type="Node" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer/Alpha"] +script = ExtResource("3_hkwup") +validator = SubResource("Resource_15man") + +[node name="AlphaError" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer"] +visible = false +layout_mode = 2 +text = "ERROR" +label_settings = SubResource("LabelSettings_su7t3") +autowrap_mode = 2 + +[node name="AlphaValid" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer"] +visible = false +layout_mode = 2 +text = "Valid!" +label_settings = SubResource("LabelSettings_urp4f") + +[node name="Label2" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Numeric" +vertical_alignment = 1 + +[node name="VBoxContainer2" type="VBoxContainer" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Numeric" type="LineEdit" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer2"] +layout_mode = 2 + +[node name="ControlValidator" type="Node" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer2/Numeric"] +script = ExtResource("3_hkwup") +validator = SubResource("Resource_1t3q0") + +[node name="NumericError" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer2"] +visible = false +layout_mode = 2 +text = "ERROR" +label_settings = SubResource("LabelSettings_su7t3") +autowrap_mode = 2 + +[node name="NumericValid" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer2"] +visible = false +layout_mode = 2 +text = "Valid!" +label_settings = SubResource("LabelSettings_urp4f") + +[node name="Label3" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Alphanumeric" +vertical_alignment = 1 + +[node name="VBoxContainer3" type="VBoxContainer" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Alphanumeric" type="LineEdit" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer3"] +layout_mode = 2 + +[node name="ControlValidator" type="Node" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer3/Alphanumeric"] +script = ExtResource("3_hkwup") +validator = SubResource("Resource_61y5a") + +[node name="AlphanumericError" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer3"] +visible = false +layout_mode = 2 +text = "ERROR" +label_settings = SubResource("LabelSettings_su7t3") +autowrap_mode = 2 + +[node name="AlphanumericValid" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer3"] +visible = false +layout_mode = 2 +text = "Valid!" +label_settings = SubResource("LabelSettings_urp4f") + +[node name="Label4" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Email" +vertical_alignment = 1 + +[node name="VBoxContainer4" type="VBoxContainer" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Email" type="LineEdit" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer4"] +layout_mode = 2 + +[node name="ControlValidator" type="Node" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer4/Email"] +script = ExtResource("3_hkwup") +validator = SubResource("Resource_q4oax") + +[node name="EmailError" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer4"] +visible = false +layout_mode = 2 +text = "ERROR" +label_settings = SubResource("LabelSettings_su7t3") +autowrap_mode = 2 + +[node name="EmailValid" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer4"] +visible = false +layout_mode = 2 +text = "Valid!" +label_settings = SubResource("LabelSettings_urp4f") + +[node name="Label5" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Length" +vertical_alignment = 1 + +[node name="VBoxContainer5" type="VBoxContainer" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Length" type="LineEdit" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer5"] +layout_mode = 2 + +[node name="ControlValidator" type="Node" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer5/Length"] +script = ExtResource("3_hkwup") +validator = SubResource("Resource_62odm") + +[node name="LengthError" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer5"] +visible = false +layout_mode = 2 +text = "ERROR" +label_settings = SubResource("LabelSettings_su7t3") +autowrap_mode = 2 + +[node name="LengthValid" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer5"] +visible = false +layout_mode = 2 +text = "Valid!" +label_settings = SubResource("LabelSettings_urp4f") + +[node name="Label6" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Value" +vertical_alignment = 1 + +[node name="VBoxContainer6" type="VBoxContainer" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Value" type="SpinBox" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer6"] +layout_mode = 2 + +[node name="ControlValidator" type="Node" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer6/Value"] +script = ExtResource("3_hkwup") +validator = SubResource("Resource_smogv") + +[node name="ValueError" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer6"] +visible = false +layout_mode = 2 +text = "ERROR" +label_settings = SubResource("LabelSettings_su7t3") +autowrap_mode = 2 + +[node name="ValueValid" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer6"] +visible = false +layout_mode = 2 +text = "Valid!" +label_settings = SubResource("LabelSettings_urp4f") + +[node name="Label7" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Custom" +vertical_alignment = 1 + +[node name="VBoxContainer7" type="VBoxContainer" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ColorPicker" type="ColorPickerButton" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer7"] +custom_minimum_size = Vector2(0, 32) +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ControlValidator" type="Node" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer7/ColorPicker"] +script = ExtResource("3_hkwup") +validator = SubResource("Resource_mh7td") + +[node name="ColorPickerError" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer7"] +visible = false +layout_mode = 2 +text = "ERROR" +label_settings = SubResource("LabelSettings_su7t3") +autowrap_mode = 2 + +[node name="ColorPickerValid" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer7"] +visible = false +layout_mode = 2 +text = "Valid!" +label_settings = SubResource("LabelSettings_urp4f") + +[node name="Label8" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +text = "Checked" +vertical_alignment = 1 + +[node name="VBoxContainer8" type="VBoxContainer" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Checked" type="CheckBox" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer8"] +custom_minimum_size = Vector2(0, 32) +layout_mode = 2 +size_flags_vertical = 3 + +[node name="ControlValidator" type="Node" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer8/Checked"] +script = ExtResource("3_hkwup") +validator = SubResource("Resource_kbsj8") + +[node name="CheckedError" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer8"] +visible = false +layout_mode = 2 +text = "ERROR" +label_settings = SubResource("LabelSettings_su7t3") +autowrap_mode = 2 + +[node name="CheckedValid" type="Label" parent="MarginContainer/FormValidator/VBoxContainer2/GridContainer/VBoxContainer8"] +visible = false +layout_mode = 2 +text = "Valid!" +label_settings = SubResource("LabelSettings_urp4f") + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/FormValidator/VBoxContainer2"] +layout_mode = 2 +theme_override_constants/separation = 32 + +[node name="Validate" type="Button" parent="MarginContainer/FormValidator/VBoxContainer2/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 16 +text = "Validate Form" + +[node name="Exit" type="Button" parent="MarginContainer/FormValidator/VBoxContainer2/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 16 +text = "Exit" + +[connection signal="control_validated" from="MarginContainer/FormValidator" to="." method="_on_form_validator_control_control_validated"] +[connection signal="pressed" from="MarginContainer/FormValidator/VBoxContainer2/HBoxContainer/Validate" to="." method="_on_validate_pressed"] +[connection signal="pressed" from="MarginContainer/FormValidator/VBoxContainer2/HBoxContainer/Exit" to="." method="_on_exit_pressed"] diff --git a/addons/godot-form-validator/form_validator.gd b/addons/godot-form-validator/form_validator.gd new file mode 100644 index 00000000..865ac114 --- /dev/null +++ b/addons/godot-form-validator/form_validator.gd @@ -0,0 +1,125 @@ +@tool +extends Control +class_name FormValidator + +signal control_validated(control, passed, messages) + +class ValidatorInfo extends RefCounted: + var control: Control + var validator: Validator + +@export var auto_validate: bool = false +@export var validation_method: Validation.Method = Validation.Method.BATCH: + set(value): + validation_method = value + _update_validation_methods() + +var _control_validator_map: Dictionary = {} +var _control_messages_map: Dictionary = {} + + +func _ready() -> void: + Validation.validator_added.connect(_on_validator_added) + Validation.validator_removed.connect(_on_validator_removed) + _find_validators(self) + + +func get_messages() -> Dictionary: + return _control_messages_map + + +func get_messages_for_control(control: Control) -> PackedStringArray: + if not _control_messages_map.has(control): + return PackedStringArray() + return _control_messages_map[control] + + +func validate() -> bool: + _control_messages_map.clear() + var list = _get_validator_info_list() + var valid = true + for info in list: + if info.validator.skip_validation: + continue + var passed = info.validator.validate(info.control) + var messages = info.validator.get_messages() + if not passed: + _control_messages_map[info.control] = messages + control_validated.emit(info.control, passed, messages) + valid = valid and passed + if not valid and validation_method == Validation.Method.IMMEDIATE: + return valid + return valid + + +func _get_validator_info_list() -> Array[ValidatorInfo]: + var list: Array[ValidatorInfo] = [] + for control in _control_validator_map.keys(): + var validator = _control_validator_map[control] + if not validator: + continue + var info = ValidatorInfo.new() + info.control = control + info.validator = validator + list.append(info) + list.sort_custom(func (a: ValidatorInfo, b: ValidatorInfo): + return a.validator.validation_order < b.validator.validation_order + ) + return list + + +func _update_validation_methods() -> void: + var validators = _control_validator_map.values() + for item in validators: + var validator = item as Validator + if not validator: + continue + validator.validation_method = validation_method + + +func _find_validators(node: Node) -> void: + for child in node.get_children(): + var control_validator = child as ControlValidator + if control_validator: + control_validator._on_validator_added() + _find_validators(child) + + +func _auto_validate(control: Control) -> void: + if not auto_validate: + return + var validator = _control_validator_map[control] as Validator + if not validator: + return + var passed = validator.validate(control) + control_validated.emit(control, passed, validator.get_messages()) + + +func _on_validator_added(control: Control, validator: Validator) -> void: + if not control: + return + _control_validator_map[control] = validator + if not control.focus_exited.is_connected(_on_control_focus_exited): + control.focus_exited.connect(_on_control_focus_exited.bind(control)) + # Special case to handle range-type controls that don't respond to focus + # events for editing. + if control.has_signal("value_changed"): + if not control.value_changed.is_connected(_on_control_value_changed): + control.value_changed.connect(_on_control_value_changed.bind(control)) + + +func _on_validator_removed(control: Control) -> void: + _control_validator_map.erase(control) + if control.focus_exited.is_connected(_on_control_focus_exited): + control.focus_exited.disconnect(_on_control_focus_exited) + if control.has_signal("value_changed"): + if control.value_changed.is_connected(_on_control_value_changed): + control.value_changed.disconnect(_on_control_value_changed) + + +func _on_control_focus_exited(control: Control) -> void: + _auto_validate(control) + + +func _on_control_value_changed(value: float, control: Control) -> void: + _auto_validate(control) diff --git a/addons/godot-form-validator/plugin.cfg b/addons/godot-form-validator/plugin.cfg new file mode 100644 index 00000000..1500bd14 --- /dev/null +++ b/addons/godot-form-validator/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Godot Form Validator" +description="A plugin for Godot 4 that adds robust validation logic to any control." +author="deadpixelsociety" +version="1.0" +script="plugin.gd" diff --git a/addons/godot-form-validator/plugin.gd b/addons/godot-form-validator/plugin.gd new file mode 100644 index 00000000..0b997ff3 --- /dev/null +++ b/addons/godot-form-validator/plugin.gd @@ -0,0 +1,14 @@ +@tool +extends EditorPlugin + + +func _enter_tree() -> void: + add_autoload_singleton("Validation", "res://addons/godot-form-validator/validation.gd") + add_custom_type("FormValidator", "Control", preload("form_validator.gd"), preload("editor_icon.png")) + add_custom_type("ControlValidator", "Node", preload("control_validator.gd"), preload("editor_icon.png")) + + +func _exit_tree() -> void: + remove_custom_type("ControlValidator") + remove_custom_type("FormValidator") + remove_autoload_singleton("Validation") diff --git a/addons/godot-form-validator/rules/alpha_rule.gd b/addons/godot-form-validator/rules/alpha_rule.gd new file mode 100644 index 00000000..29ef594c --- /dev/null +++ b/addons/godot-form-validator/rules/alpha_rule.gd @@ -0,0 +1,16 @@ +@tool +extends ValidatorRule +class_name AlphaRule + + +func _init() -> void: + fail_message = "Value must contain only alpha characters." + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if value is String: + result.passed = ValidatorFunctions.empty(value) or ValidatorFunctions.alpha(value) + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/alphanumeric_rule.gd b/addons/godot-form-validator/rules/alphanumeric_rule.gd new file mode 100644 index 00000000..6da36301 --- /dev/null +++ b/addons/godot-form-validator/rules/alphanumeric_rule.gd @@ -0,0 +1,16 @@ +@tool +extends ValidatorRule +class_name AlphanumericRule + + +func _init() -> void: + fail_message = "Value must contain only alphanumeric cahracters." + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if value is String: + result.passed = ValidatorFunctions.empty(value) or ValidatorFunctions.alphanumeric(value) + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/boolean_rule.gd b/addons/godot-form-validator/rules/boolean_rule.gd new file mode 100644 index 00000000..b80a11a2 --- /dev/null +++ b/addons/godot-form-validator/rules/boolean_rule.gd @@ -0,0 +1,15 @@ +@tool +extends ValidatorRule +class_name BooleanRule + +@export var target_value: bool + + +func apply(control: Control, value) -> RuleResult: + if ValidatorFunctions.empty(fail_message): + fail_message = "The value must equal %s." % target_value + var result = RuleResult.new() + result.passed = value is bool and value == target_value + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/custom_rule.gd b/addons/godot-form-validator/rules/custom_rule.gd new file mode 100644 index 00000000..c6c50758 --- /dev/null +++ b/addons/godot-form-validator/rules/custom_rule.gd @@ -0,0 +1,36 @@ +@tool +extends ValidatorRule +class_name CustomRule + +@export_multiline var expression: String = "" + +var _expression: Expression = Expression.new() + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if not _validate_expression(): + result.passed = false + result.message = "Specified expression is not valid." + return result + var res = _expression.execute([ control, value ]) + if _expression.has_execute_failed(): + result.passed = false + result.message = "Execution of the specified expression has failed." + return result + result.passed = res == true + if not result.passed: + result.message = fail_message + return result + + +func is_valid() -> bool: + return _validate_expression() + + +func get_invalid_message() -> String: + return "Specified expression is not valid." + + +func _validate_expression() -> bool: + return _expression.parse(expression, [ "control", "value" ]) == OK diff --git a/addons/godot-form-validator/rules/does_not_match_rule.gd b/addons/godot-form-validator/rules/does_not_match_rule.gd new file mode 100644 index 00000000..b8e91756 --- /dev/null +++ b/addons/godot-form-validator/rules/does_not_match_rule.gd @@ -0,0 +1,26 @@ +@tool +extends ValidatorRule +class_name DoesNotMatchRule + +@export var pattern: String + + +func _init() -> void: + fail_message = "Invalid value." + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if value is String: + result.passed = ValidatorFunctions.empty(value) or ValidatorFunctions.does_not_match(pattern, value) + if not result.passed: + result.message = fail_message + return result + + +func is_valid() -> bool: + return RegEx.create_from_string(pattern).is_valid() + + +func get_invalid_message() -> String: + return "Specified regular expression is not valid." diff --git a/addons/godot-form-validator/rules/email_rule.gd b/addons/godot-form-validator/rules/email_rule.gd new file mode 100644 index 00000000..f32111bb --- /dev/null +++ b/addons/godot-form-validator/rules/email_rule.gd @@ -0,0 +1,16 @@ +@tool +extends ValidatorRule +class_name EmailRule + + +func _init() -> void: + fail_message = "Value must be a valid email address." + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if value is String: + result.passed = ValidatorFunctions.empty(value) or ValidatorFunctions.email(value) + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/equals_rule.gd b/addons/godot-form-validator/rules/equals_rule.gd new file mode 100644 index 00000000..2a08436d --- /dev/null +++ b/addons/godot-form-validator/rules/equals_rule.gd @@ -0,0 +1,15 @@ +@tool +extends ValidatorRule +class_name EqualsRule + +@export var target_value: String + + +func apply(control: Control, value) -> RuleResult: + if ValidatorFunctions.empty(fail_message): + fail_message = "The value must equal %s." % target_value + var result = RuleResult.new() + result.passed = str(value) == target_value + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/greater_than_rule.gd b/addons/godot-form-validator/rules/greater_than_rule.gd new file mode 100644 index 00000000..e80a807b --- /dev/null +++ b/addons/godot-form-validator/rules/greater_than_rule.gd @@ -0,0 +1,15 @@ +@tool +extends ValidatorRule +class_name GreaterThanRule + +@export var target_value: String + + +func apply(control: Control, value) -> RuleResult: + if ValidatorFunctions.empty(fail_message): + fail_message = "The value must be greater than %s." % target_value + var result = RuleResult.new() + result.passed = str(value) > target_value + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/length_rule.gd b/addons/godot-form-validator/rules/length_rule.gd new file mode 100644 index 00000000..eb638456 --- /dev/null +++ b/addons/godot-form-validator/rules/length_rule.gd @@ -0,0 +1,28 @@ +@tool +extends ValidatorRule +class_name LengthRule + +const FAIL_MESSAGE: String = "The value must be between %d and %d characters long." + +@export var min_length: int = 0 +@export var max_length: int = 9999 + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if value is String: + result.passed = ValidatorFunctions.length(value, min_length, max_length) + if not result.passed: + if ValidatorFunctions.empty(fail_message): + result.message = FAIL_MESSAGE % [ min_length, max_length ] + else: + result.message = fail_message + return result + + +func is_valid() -> bool: + return min_length <= max_length + + +func get_invalid_message() -> String: + return "Minimum length must be less than or equal to maximum length." diff --git a/addons/godot-form-validator/rules/less_than_rule.gd b/addons/godot-form-validator/rules/less_than_rule.gd new file mode 100644 index 00000000..9b069736 --- /dev/null +++ b/addons/godot-form-validator/rules/less_than_rule.gd @@ -0,0 +1,15 @@ +@tool +extends ValidatorRule +class_name LessThanRule + +@export var target_value: String + + +func apply(control: Control, value) -> RuleResult: + if ValidatorFunctions.empty(fail_message): + fail_message = "The value must be less than %s." % target_value + var result = RuleResult.new() + result.passed = str(value) < target_value + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/matches_rule.gd b/addons/godot-form-validator/rules/matches_rule.gd new file mode 100644 index 00000000..b222b8e8 --- /dev/null +++ b/addons/godot-form-validator/rules/matches_rule.gd @@ -0,0 +1,26 @@ +@tool +extends ValidatorRule +class_name MatchesRule + +@export var pattern: String + + +func _init() -> void: + fail_message = "Invalid value." + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if value is String: + result.passed = ValidatorFunctions.empty(value) or ValidatorFunctions.matches(pattern, value) + if not result.passed: + result.message = fail_message + return result + + +func is_valid() -> bool: + return RegEx.create_from_string(pattern).is_valid() + + +func get_invalid_message() -> String: + return "Specified regular expression is not valid." diff --git a/addons/godot-form-validator/rules/not_blank_rule.gd b/addons/godot-form-validator/rules/not_blank_rule.gd new file mode 100644 index 00000000..5b47874e --- /dev/null +++ b/addons/godot-form-validator/rules/not_blank_rule.gd @@ -0,0 +1,16 @@ +@tool +extends ValidatorRule +class_name NotBlankRule + + +func _init() -> void: + fail_message = "Value must not be blank." + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if value is String: + result.passed = ValidatorFunctions.not_blank(value) + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/not_empty_rule.gd b/addons/godot-form-validator/rules/not_empty_rule.gd new file mode 100644 index 00000000..e8db0100 --- /dev/null +++ b/addons/godot-form-validator/rules/not_empty_rule.gd @@ -0,0 +1,16 @@ +@tool +extends ValidatorRule +class_name NotEmptyRule + + +func _init() -> void: + fail_message = "Value must not be empty." + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if value is String: + result.passed = ValidatorFunctions.not_empty(value) + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/numeric_rule.gd b/addons/godot-form-validator/rules/numeric_rule.gd new file mode 100644 index 00000000..003e4f8c --- /dev/null +++ b/addons/godot-form-validator/rules/numeric_rule.gd @@ -0,0 +1,16 @@ +@tool +extends ValidatorRule +class_name NumericRule + + +func _init() -> void: + fail_message = "Value must contain only numbers." + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if value is String: + result.passed = ValidatorFunctions.empty(value) or ValidatorFunctions.numeric(value) + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/required_rule.gd b/addons/godot-form-validator/rules/required_rule.gd new file mode 100644 index 00000000..f6cc0edd --- /dev/null +++ b/addons/godot-form-validator/rules/required_rule.gd @@ -0,0 +1,18 @@ +@tool +extends ValidatorRule +class_name RequiredRule + + +func _init(): + fail_message = "A value is required." + + +func apply(control: Control, value) -> RuleResult: + var result = RuleResult.new() + if value is String: + result.passed = ValidatorFunctions.not_blank(value) + else: + result.passed = value != null + if not result.passed: + result.message = fail_message + return result diff --git a/addons/godot-form-validator/rules/rule_result.gd b/addons/godot-form-validator/rules/rule_result.gd new file mode 100644 index 00000000..3b4800cd --- /dev/null +++ b/addons/godot-form-validator/rules/rule_result.gd @@ -0,0 +1,5 @@ +extends RefCounted +class_name RuleResult + +var passed: bool = false +var message: String = "" diff --git a/addons/godot-form-validator/rules/validator_rule.gd b/addons/godot-form-validator/rules/validator_rule.gd new file mode 100644 index 00000000..fe97db07 --- /dev/null +++ b/addons/godot-form-validator/rules/validator_rule.gd @@ -0,0 +1,17 @@ +@tool +extends Resource +class_name ValidatorRule + +@export var fail_message: String = "" + + +func apply(control: Control, value) -> RuleResult: + return RuleResult.new() + + +func is_valid() -> bool: + return true + + +func get_invalid_message() -> String: + return "" diff --git a/addons/godot-form-validator/validation.gd b/addons/godot-form-validator/validation.gd new file mode 100644 index 00000000..a334e93f --- /dev/null +++ b/addons/godot-form-validator/validation.gd @@ -0,0 +1,39 @@ +@tool +extends Node + +signal validator_added(control, validator) +signal validator_removed(control) + +enum Method { + # Processes all validators and rules before returning a result. + BATCH, + # Processes validators until a rule failure is encountered, then proceeds to the next validator. + FAIL_FAST, + # Processes validators until a rule failure is encountered and returns immediately. + IMMEDIATE +} + +var _validators: Array[Validator] = [] + + +func _ready() -> void: + add_validator(ButtonValidator.new()) + add_validator(LineEditValidator.new()) + add_validator(RangeValidator.new()) + add_validator(TextEditValidator.new()) + + +func add_validator(validator: Validator) -> void: + if not _validators.has(validator): + _validators.append(validator) + + +func get_validators() -> Array[Validator]: + return _validators + + +func find_validator(node: Node) -> Validator: + for validator in get_validators(): + if validator.is_type(node): + return validator + return null diff --git a/addons/godot-form-validator/validator_functions.gd b/addons/godot-form-validator/validator_functions.gd new file mode 100644 index 00000000..cd11b04a --- /dev/null +++ b/addons/godot-form-validator/validator_functions.gd @@ -0,0 +1,66 @@ +extends Object +class_name ValidatorFunctions + + +static func matches(pattern: String, text: String) -> bool: + var regex = RegEx.create_from_string(pattern) + if not regex.is_valid(): + print_debug("WARNING: Invalid RegEx pattern supplied to matches function: ", pattern) + return false + var result = regex.search(text) + return result != null and result.strings.size() > 0 + + +static func does_not_match(pattern: String, text: String) -> bool: + var regex = RegEx.create_from_string(pattern) + if not regex.is_valid(): + print_debug("WARNING: Invalid RegEx pattern supplied to matches function: ", pattern) + return false + var result = regex.search(text) + return result == null + + +static func not_empty(text: String) -> bool: + return text != null and not text.is_empty() + + +static func empty(text: String) -> bool: + return not not_empty(text) + + +static func not_blank(text: String) -> bool: + if text == null: + return false + # Remove escape sequences + text = text.strip_escapes() + # Remove spaces + text = text.replace(" ", "") + return not text.is_empty() + + +static func alpha(text: String) -> bool: + var regex = RegEx.create_from_string("[^a-zA-Z]+") + var result = regex.search(text) + return result == null + + +static func numeric(text: String) -> bool: + var regex = RegEx.create_from_string("[^0-9]+") + var result = regex.search(text) + return result == null + + +static func alphanumeric(text: String) -> bool: + var regex = RegEx.create_from_string("[^a-zA-Z0-9]+") + var result = regex.search(text) + return result == null + + +static func email(text: String) -> bool: + var regex = RegEx.create_from_string("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$") + var result = regex.search(text) + return result != null and result.strings.size() > 0 + + +static func length(text: String, min_length: int = 0, max_length: int = 9999) -> bool: + return text != null and text.length() >= min_length and text.length() <= max_length diff --git a/addons/godot-form-validator/validators/button_validator.gd b/addons/godot-form-validator/validators/button_validator.gd new file mode 100644 index 00000000..651a527c --- /dev/null +++ b/addons/godot-form-validator/validators/button_validator.gd @@ -0,0 +1,14 @@ +@tool +extends Validator +class_name ButtonValidator + + +func get_value(control: Control): + var button = control as Button + if not button: + return null + return button.button_pressed + + +func is_type(node) -> bool: + return node is Button diff --git a/addons/godot-form-validator/validators/line_edit_validator.gd b/addons/godot-form-validator/validators/line_edit_validator.gd new file mode 100644 index 00000000..b5665bbe --- /dev/null +++ b/addons/godot-form-validator/validators/line_edit_validator.gd @@ -0,0 +1,14 @@ +@tool +extends Validator +class_name LineEditValidator + + +func get_value(control: Control): + var line_edit = control as LineEdit + if not line_edit: + return null + return line_edit.text + + +func is_type(node) -> bool: + return node is LineEdit diff --git a/addons/godot-form-validator/validators/range_validator.gd b/addons/godot-form-validator/validators/range_validator.gd new file mode 100644 index 00000000..46074fb6 --- /dev/null +++ b/addons/godot-form-validator/validators/range_validator.gd @@ -0,0 +1,15 @@ +@tool +extends Validator +class_name RangeValidator + + +func get_value(control: Control): + var range_control = control as Range + if not range_control: + return null + return range_control.value + + +func is_type(node) -> bool: + return node is Range and \ + not (node is ScrollBar or node is ProgressBar or node is TextureProgressBar) diff --git a/addons/godot-form-validator/validators/text_edit_validator.gd b/addons/godot-form-validator/validators/text_edit_validator.gd new file mode 100644 index 00000000..ef4d2579 --- /dev/null +++ b/addons/godot-form-validator/validators/text_edit_validator.gd @@ -0,0 +1,14 @@ +@tool +extends Validator +class_name TextEditValidator + + +func get_value(control: Control): + var text_edit = control as TextEdit + if not text_edit: + return null + return text_edit.text + + +func is_type(node) -> bool: + return node is TextEdit diff --git a/addons/godot-form-validator/validators/validator.gd b/addons/godot-form-validator/validators/validator.gd new file mode 100644 index 00000000..0b0aeb92 --- /dev/null +++ b/addons/godot-form-validator/validators/validator.gd @@ -0,0 +1,37 @@ +@tool +extends Resource +class_name Validator + +@export var validation_order: int = 1 +@export var validation_method: Validation.Method = Validation.Method.BATCH +@export var skip_validation: bool = false +@export var rules: Array[ValidatorRule] = [] + +var _messages: PackedStringArray = PackedStringArray() + + +func get_value(control: Control): + return null + + +func is_type(node) -> bool: + return node is Control + + +func get_messages() -> PackedStringArray: + return _messages + + +func validate(control: Control) -> bool: + _messages.clear() + var valid = true + for rule in rules: + var result = rule.apply(control, get_value(control)) + if not result.passed: + _messages.append(result.message) + valid = valid and result.passed + if not valid: + # Not BATCH, exit early. + if validation_method != Validation.Method.BATCH: + return valid + return valid diff --git a/assets/bonuspath_a_01_complete.png b/assets/bonuspath_a_01_complete.png new file mode 100644 index 00000000..ffbc0a2f Binary files /dev/null and b/assets/bonuspath_a_01_complete.png differ diff --git a/assets/bonuspath_a_01_complete.png.import b/assets/bonuspath_a_01_complete.png.import new file mode 100644 index 00000000..97a4737d --- /dev/null +++ b/assets/bonuspath_a_01_complete.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cuk6aur2mqe6y" +path="res://.godot/imported/bonuspath_a_01_complete.png-897bda4de5428c7b46c5eb63574d356c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/bonuspath_a_01_complete.png" +dest_files=["res://.godot/imported/bonuspath_a_01_complete.png-897bda4de5428c7b46c5eb63574d356c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/bonuspath_a_01_not_complete.png b/assets/bonuspath_a_01_not_complete.png new file mode 100644 index 00000000..eb55749b Binary files /dev/null and b/assets/bonuspath_a_01_not_complete.png differ diff --git a/assets/bonuspath_a_01_not_complete.png.import b/assets/bonuspath_a_01_not_complete.png.import new file mode 100644 index 00000000..40840c0c --- /dev/null +++ b/assets/bonuspath_a_01_not_complete.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://blxcf17gus8c" +path="res://.godot/imported/bonuspath_a_01_not_complete.png-64b0f6f0d472897aab337e8a484b9938.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/bonuspath_a_01_not_complete.png" +dest_files=["res://.godot/imported/bonuspath_a_01_not_complete.png-64b0f6f0d472897aab337e8a484b9938.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/path_a_01.png b/assets/path_a_01.png new file mode 100644 index 00000000..a1b49a36 Binary files /dev/null and b/assets/path_a_01.png differ diff --git a/assets/path_a_01.png.import b/assets/path_a_01.png.import new file mode 100644 index 00000000..ec69ee2b --- /dev/null +++ b/assets/path_a_01.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bb080qc6juomq" +path="res://.godot/imported/path_a_01.png-93a1b4c2adfbc89d6517f795a9e687a9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/path_a_01.png" +dest_files=["res://.godot/imported/path_a_01.png-93a1b4c2adfbc89d6517f795a9e687a9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/path_b_01.png b/assets/path_b_01.png new file mode 100644 index 00000000..675bc47d Binary files /dev/null and b/assets/path_b_01.png differ diff --git a/assets/path_b_01.png.import b/assets/path_b_01.png.import new file mode 100644 index 00000000..f5fc7c4e --- /dev/null +++ b/assets/path_b_01.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://byv4d55but7c" +path="res://.godot/imported/path_b_01.png-17f14a7bc673650b104023018c190eb3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/path_b_01.png" +dest_files=["res://.godot/imported/path_b_01.png-17f14a7bc673650b104023018c190eb3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/example_rules.png b/example_rules.png new file mode 100644 index 00000000..97df4533 Binary files /dev/null and b/example_rules.png differ diff --git a/example_rules.png.import b/example_rules.png.import new file mode 100644 index 00000000..f902bd89 --- /dev/null +++ b/example_rules.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c13odbsfv53he" +path="res://.godot/imported/example_rules.png-faa9d2703b3ffa116338aa78beef009e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://example_rules.png" +dest_files=["res://.godot/imported/example_rules.png-faa9d2703b3ffa116338aa78beef009e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/example_tree.png b/example_tree.png new file mode 100644 index 00000000..1bd14975 Binary files /dev/null and b/example_tree.png differ diff --git a/example_tree.png.import b/example_tree.png.import new file mode 100644 index 00000000..b61077f6 --- /dev/null +++ b/example_tree.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b1ynfmrih7ira" +path="res://.godot/imported/example_tree.png-f962c768c2ced2db3f35399da01825e8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://example_tree.png" +dest_files=["res://.godot/imported/example_tree.png-f962c768c2ced2db3f35399da01825e8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/icon.png b/icon.png new file mode 100644 index 00000000..f7a61a88 Binary files /dev/null and b/icon.png differ diff --git a/icon.png.import b/icon.png.import new file mode 100644 index 00000000..d38623ef --- /dev/null +++ b/icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d3n0k21jubf3p" +path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.png" +dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/logo.png b/logo.png new file mode 100644 index 00000000..c387223c Binary files /dev/null and b/logo.png differ diff --git a/logo.png.import b/logo.png.import new file mode 100644 index 00000000..a29e402f --- /dev/null +++ b/logo.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3ykco3r3cbm4" +path="res://.godot/imported/logo.png-cca8726399059c8d4f806e28e356b14d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://logo.png" +dest_files=["res://.godot/imported/logo.png-cca8726399059c8d4f806e28e356b14d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/project.godot b/project.godot index 62915957..94bace7c 100644 --- a/project.godot +++ b/project.godot @@ -29,6 +29,8 @@ LessonLogger="*res://sources/utils/autoloads/lesson_logger.gd" Database="*res://sources/utils/autoloads/Database.gd" UserDataManager="*res://sources/utils/autoloads/user_data_manager.gd" MusicManager="*res://sources/utils/autoloads/music_manager.tscn" +LessonLogger="*res://sources/utils/autoloads/lesson_logger.gd" +Validation="*res://addons/godot-form-validator/validation.gd" [display] @@ -40,7 +42,7 @@ window/stretch/mode="canvas_items" [editor_plugins] -enabled=PackedStringArray("res://addons/godot-sqlite/plugin.cfg") +enabled=PackedStringArray("res://addons/godot-form-validator/plugin.cfg", "res://addons/godot-sqlite/plugin.cfg") [gui] diff --git a/sources/menus/register/register_question.gd b/sources/menus/register/register_question.gd new file mode 100644 index 00000000..afedfdb9 --- /dev/null +++ b/sources/menus/register/register_question.gd @@ -0,0 +1,4 @@ +extends Resource +class_name RegisterQuestion + +var txt : String diff --git a/sources/menus/register/register_screen.gd b/sources/menus/register/register_screen.gd new file mode 100644 index 00000000..feff5486 --- /dev/null +++ b/sources/menus/register/register_screen.gd @@ -0,0 +1,79 @@ +extends Control + +@onready var teacher_steps : Array[PackedScene] = [ + preload("res://sources/menus/register/steps/teacher/method_step.tscn"), + preload("res://sources/menus/register/steps/teacher/devices_count_step.tscn"), + preload("res://sources/menus/register/steps/teacher/students_count_step.tscn"), + preload("res://sources/menus/register/steps/credentials_step.tscn") +] +@onready var parent_steps : Array[PackedScene] = [ + +] +var current_steps : Array[PackedScene] +# TODO Change the resource +@onready var register_data := UserSettings.new() +@onready var progress_bar := %ProgressBar +@onready var steps := %Steps + +func _ready(): + current_steps = teacher_steps + + # Handles the progress bar + progress_bar.value = 0 + progress_bar.max_value = current_steps.size() + + # Load new step + _go_to_step(progress_bar.value) + + +func _go_to_step(step_index: int): + var next_step + + # Check if step is already instantiated + for step in steps.get_children(false): + step.hide() + + if step.name == str(progress_bar.value): + step.completed.disconnect(_on_step_completed) + pass + + if step.name == str(step_index): + next_step = step + + # Instantiate the step + if not next_step: + next_step = teacher_steps[step_index].instantiate() + next_step.name = str(step_index) + next_step.object = register_data + steps.add_child(next_step) + else: + next_step.show() + + # Connect the step + next_step.completed.connect(_on_step_completed) + + # Handles progress bar + progress_bar.value = step_index + + +func _reset_steps(): + progress_bar.value = 0 + progress_bar.max_value = 0 + + for step in steps.get_children(false): + step.queue_free() + + +func _on_back_button_pressed(): + if progress_bar.value == 0: + print("Back to title screen") + # get_tree().change_scene_to_file() + else: + _go_to_step(progress_bar.value-1) + + +func _on_step_completed(): + if progress_bar.value == current_steps.size()-1: + print("Registering complete !") + else: + _go_to_step(progress_bar.value+1) diff --git a/sources/menus/register/register_screen.tscn b/sources/menus/register/register_screen.tscn new file mode 100644 index 00000000..9ba9dd53 --- /dev/null +++ b/sources/menus/register/register_screen.tscn @@ -0,0 +1,75 @@ +[gd_scene load_steps=7 format=3 uid="uid://b7cfjs2iipagq"] + +[ext_resource type="Texture2D" uid="uid://wtx3wqs0l4si" path="res://assets/minigames/minigame_ui/graphic/button_back_up.png" id="1_g16n6"] +[ext_resource type="Script" path="res://sources/menus/register/register_screen.gd" id="1_the3k"] +[ext_resource type="Texture2D" uid="uid://cbhip35yyqneg" path="res://assets/minigames/minigame_ui/graphic/button_back_down.png" id="2_ufjlu"] +[ext_resource type="Texture2D" uid="uid://tcapan0mj64o" path="res://assets/minigames/minigame_ui/graphic/button_back_disabled.png" id="3_svtfb"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0rcyk"] +border_width_left = 3 +border_width_top = 3 +border_width_right = 3 +border_width_bottom = 3 +expand_margin_top = 3.0 +expand_margin_bottom = 3.0 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bhfkq"] +bg_color = Color(0, 0.607843, 0.831373, 1) + +[node name="RegisterScreen" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_the3k") + +[node name="ProgressBarContainer" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 10 +anchor_right = 1.0 +offset_bottom = 256.0 +grow_horizontal = 2 +theme_override_constants/margin_left = 25 +theme_override_constants/margin_top = 50 +theme_override_constants/margin_right = 231 +theme_override_constants/margin_bottom = 50 + +[node name="HBoxContainer" type="HBoxContainer" parent="ProgressBarContainer"] +layout_mode = 2 +theme_override_constants/separation = 50 + +[node name="BackButton" type="TextureButton" parent="ProgressBarContainer/HBoxContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(156, 156) +layout_mode = 2 +size_flags_horizontal = 0 +texture_normal = ExtResource("1_g16n6") +texture_pressed = ExtResource("2_ufjlu") +texture_disabled = ExtResource("3_svtfb") +stretch_mode = 5 + +[node name="ProgressBar" type="ProgressBar" parent="ProgressBarContainer/HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 4 +theme_override_colors/font_color = Color(0, 0, 0, 0) +theme_override_colors/font_outline_color = Color(0, 0, 0, 0) +theme_override_styles/background = SubResource("StyleBoxFlat_0rcyk") +theme_override_styles/fill = SubResource("StyleBoxFlat_bhfkq") +step = 1.0 +value = 50.0 + +[node name="Steps" type="Control" parent="."] +unique_name_in_owner = true +layout_mode = 1 +anchor_top = 0.142222 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +metadata/_edit_use_anchors_ = true + +[connection signal="pressed" from="ProgressBarContainer/HBoxContainer/BackButton" to="." method="_on_back_button_pressed"] diff --git a/sources/menus/register/steps/base_step.gd b/sources/menus/register/steps/base_step.gd new file mode 100644 index 00000000..a4c8218f --- /dev/null +++ b/sources/menus/register/steps/base_step.gd @@ -0,0 +1,68 @@ +@tool +extends Control +class_name Step + +signal completed + +const checked_properties := [ + "value", + "text" +] + +@onready var question_label : Label = %QuestionLabel +@onready var form_validator : FormValidator = %FormValidator +@onready var form_container : Control = %FormContainer + +@export var question_txt : String +@export var object : Resource + +func _ready(): + if question_txt: + question_label.text = question_txt + + _fill_form() + + +func _fill_form(): + if not object: + return + + for node in form_container.get_children(false): + if node.name in object: + for property_name in checked_properties: + if property_name in node: + node.set(property_name, object.get(node.name)) + else: + push_warning("Property not found in object for node: " + node.name + ". All nodes inside FormContainer must reflects a property from the resource.") + + +func _write_object() -> bool: + if not object: + return false + + for node in form_container.get_children(false): + var value + if node.name in object: + for property_name in checked_properties: + if property_name in node: + value = node.get(property_name) + + if value: + object.set(node.name, value) + else: + return false + + return true + + +func _on_validate_button_pressed(): + + print(form_validator.validate()) + + # Writes data in object + if _write_object(): + # Emit completed signal + emit_signal("completed") + else: + # TODO Show error message + print("Please fill all the fields") diff --git a/sources/menus/register/steps/base_step.tscn b/sources/menus/register/steps/base_step.tscn new file mode 100644 index 00000000..00c03c78 --- /dev/null +++ b/sources/menus/register/steps/base_step.tscn @@ -0,0 +1,101 @@ +[gd_scene load_steps=3 format=3 uid="uid://bxd3i06rqpxf0"] + +[ext_resource type="Script" path="res://sources/menus/register/steps/base_step.gd" id="1_02ocl"] +[ext_resource type="Script" path="res://addons/godot-form-validator/form_validator.gd" id="2_jbdsv"] + +[node name="BaseRegisterForm" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_02ocl") + +[node name="RightMargin" type="MarginContainer" parent="."] +custom_minimum_size = Vector2(450, 2.08165e-12) +layout_mode = 1 +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -450.0 +grow_horizontal = 0 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +mouse_filter = 2 +theme_override_constants/margin_left = 50 +theme_override_constants/margin_top = 50 +theme_override_constants/margin_right = 50 +theme_override_constants/margin_bottom = 50 + +[node name="RightContainer" type="VBoxContainer" parent="RightMargin"] +layout_mode = 2 +mouse_filter = 2 +alignment = 2 + +[node name="ValidateButton" type="Button" parent="RightMargin/RightContainer"] +layout_mode = 2 +theme_override_font_sizes/font_size = 70 +text = "Next" + +[node name="PanelContainer" type="PanelContainer" parent="."] +layout_mode = 1 +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -775.0 +offset_right = 775.0 +offset_bottom = 502.0 +grow_horizontal = 2 + +[node name="QuestionContainer" type="MarginContainer" parent="PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 25 +theme_override_constants/margin_right = 25 + +[node name="QuestionLabel" type="Label" parent="PanelContainer/QuestionContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(1500, 500) +layout_mode = 2 +size_flags_vertical = 1 +theme_override_font_sizes/font_size = 70 +horizontal_alignment = 1 +vertical_alignment = 1 +autowrap_mode = 2 + +[node name="FormValidator" type="Control" parent="."] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -20.0 +offset_top = -40.0 +offset_right = 20.0 +grow_horizontal = 2 +grow_vertical = 0 +script = ExtResource("2_jbdsv") +auto_validate = true + +[node name="FormContainer" type="VBoxContainer" parent="FormValidator"] +unique_name_in_owner = true +custom_minimum_size = Vector2(1624, 812) +layout_mode = 1 +anchors_preset = 7 +anchor_left = 0.5 +anchor_top = 1.0 +anchor_right = 0.5 +anchor_bottom = 1.0 +offset_left = -812.0 +offset_top = -812.0 +offset_right = 812.0 +grow_horizontal = 2 +grow_vertical = 0 +theme_override_constants/separation = 25 +alignment = 1 + +[connection signal="pressed" from="RightMargin/RightContainer/ValidateButton" to="." method="_on_validate_button_pressed"] diff --git a/sources/menus/register/steps/credentials_step.gd b/sources/menus/register/steps/credentials_step.gd new file mode 100644 index 00000000..d0199c74 --- /dev/null +++ b/sources/menus/register/steps/credentials_step.gd @@ -0,0 +1,13 @@ +extends Step + + +@onready var password := %password +@onready var password_confirm := %password_confirm + +func _on_validate_button_pressed(): + + if password.text != password_confirm.text: + print("Make sure the password is the same in both fields") + return + + super._on_validate_button_pressed() diff --git a/sources/menus/register/steps/credentials_step.tscn b/sources/menus/register/steps/credentials_step.tscn new file mode 100644 index 00000000..7abffca2 --- /dev/null +++ b/sources/menus/register/steps/credentials_step.tscn @@ -0,0 +1,54 @@ +[gd_scene load_steps=10 format=3 uid="uid://cwqcybk5tvbly"] + +[ext_resource type="PackedScene" uid="uid://bxd3i06rqpxf0" path="res://sources/menus/register/steps/base_step.tscn" id="1_66bx5"] +[ext_resource type="Script" path="res://sources/menus/register/steps/credentials_step.gd" id="2_uwjaa"] +[ext_resource type="Script" path="res://addons/godot-form-validator/control_validator.gd" id="3_hblcf"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/required_rule.gd" id="4_4ltsd"] +[ext_resource type="Script" path="res://addons/godot-form-validator/rules/email_rule.gd" id="5_cnymo"] +[ext_resource type="Script" path="res://addons/godot-form-validator/validators/line_edit_validator.gd" id="6_viaho"] + +[sub_resource type="Resource" id="Resource_os08u"] +script = ExtResource("4_4ltsd") +fail_message = "A value is required." + +[sub_resource type="Resource" id="Resource_j7vhe"] +script = ExtResource("5_cnymo") +fail_message = "Value must be a valid email address." + +[sub_resource type="Resource" id="Resource_84kj5"] +script = ExtResource("6_viaho") +validation_order = 1 +validation_method = 0 +skip_validation = false +rules = Array[Resource("res://addons/godot-form-validator/rules/validator_rule.gd")]([SubResource("Resource_os08u"), SubResource("Resource_j7vhe")]) + +[node name="CredentialsStep" instance=ExtResource("1_66bx5")] +script = ExtResource("2_uwjaa") +question_txt = "Fill in your credentials" + +[node name="ValidateButton" parent="RightMargin/RightContainer" index="0"] +text = "Validate" + +[node name="QuestionLabel" parent="PanelContainer/QuestionContainer" index="0"] +text = "Fill in your credentials" + +[node name="email" type="LineEdit" parent="FormValidator/FormContainer" index="0"] +layout_mode = 2 +theme_override_font_sizes/font_size = 70 +placeholder_text = "Email" + +[node name="ControlValidator" type="Node" parent="FormValidator/FormContainer/email" index="0"] +script = ExtResource("3_hblcf") +validator = SubResource("Resource_84kj5") + +[node name="password" type="LineEdit" parent="FormValidator/FormContainer" index="1"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 70 +placeholder_text = "Password" + +[node name="password_confirm" type="LineEdit" parent="FormValidator/FormContainer" index="2"] +unique_name_in_owner = true +layout_mode = 2 +theme_override_font_sizes/font_size = 70 +placeholder_text = "Confirm password" diff --git a/sources/menus/register/steps/teacher/devices_count_step.tscn b/sources/menus/register/steps/teacher/devices_count_step.tscn new file mode 100644 index 00000000..4844bcaa --- /dev/null +++ b/sources/menus/register/steps/teacher/devices_count_step.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=2 format=3 uid="uid://do5e7c1oxkn5g"] + +[ext_resource type="PackedScene" uid="uid://bxd3i06rqpxf0" path="res://sources/menus/register/steps/base_step.tscn" id="1_w3urq"] + +[node name="DevicesCountStep" instance=ExtResource("1_w3urq")] +question_txt = "Choose the number of devices you are using" + +[node name="QuestionLabel" parent="PanelContainer/QuestionContainer" index="0"] +text = "Choose the number of devices you are using" + +[node name="FormContainer" parent="." index="2"] +offset_left = -812.0 +offset_top = -812.0 +offset_right = 812.0 + +[node name="devices_count" type="SpinBox" parent="FormContainer" index="0"] +layout_mode = 2 +min_value = 1.0 +max_value = 90.0 +value = 1.0 diff --git a/sources/menus/register/steps/teacher/method_step.tscn b/sources/menus/register/steps/teacher/method_step.tscn new file mode 100644 index 00000000..2396fec4 --- /dev/null +++ b/sources/menus/register/steps/teacher/method_step.tscn @@ -0,0 +1,19 @@ +[gd_scene load_steps=2 format=3 uid="uid://buhnx0oblueuq"] + +[ext_resource type="PackedScene" uid="uid://bxd3i06rqpxf0" path="res://sources/menus/register/steps/base_step.tscn" id="1_ksrhk"] + +[node name="MethodStep" instance=ExtResource("1_ksrhk")] +question_txt = "Choose the education method you are using" + +[node name="QuestionLabel" parent="PanelContainer/QuestionContainer" index="0"] +text = "Choose the education method you are using" + +[node name="method" type="ItemList" parent="FormContainer" index="0"] +layout_mode = 2 +theme_override_constants/h_separation = 25 +theme_override_constants/v_separation = 25 +theme_override_font_sizes/font_size = 70 +auto_height = true +item_count = 2 +item_0/text = "Complete" +item_1/text = "App Only" diff --git a/sources/menus/register/steps/teacher/students_count_step.tscn b/sources/menus/register/steps/teacher/students_count_step.tscn new file mode 100644 index 00000000..3780a290 --- /dev/null +++ b/sources/menus/register/steps/teacher/students_count_step.tscn @@ -0,0 +1,15 @@ +[gd_scene load_steps=2 format=3 uid="uid://cyhoa68y1yyyg"] + +[ext_resource type="PackedScene" uid="uid://bxd3i06rqpxf0" path="res://sources/menus/register/steps/base_step.tscn" id="1_par6r"] + +[node name="StudentsCountStep" instance=ExtResource("1_par6r")] +question_txt = "Choose the total number of students" + +[node name="QuestionLabel" parent="PanelContainer/QuestionContainer" index="0"] +text = "Choose the total number of students" + +[node name="students_count" type="SpinBox" parent="FormContainer" index="0"] +layout_mode = 2 +min_value = 1.0 +max_value = 90.0 +value = 1.0