Adds steps logic for the register form.

This commit is contained in:
Dylan Sarrazyn
2024-01-31 16:56:35 +01:00
parent 9abb8499e2
commit e604c51ee9
60 changed files with 1972 additions and 1 deletions
+21
View File
@@ -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.
@@ -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)
Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

@@ -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
@@ -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()
@@ -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"]
@@ -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)
+7
View File
@@ -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"
+14
View File
@@ -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")
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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."
@@ -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
@@ -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
@@ -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
@@ -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."
@@ -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
@@ -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."
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,5 @@
extends RefCounted
class_name RuleResult
var passed: bool = false
var message: String = ""
@@ -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 ""
+39
View File
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+34
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

+34
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

+34
View File
@@ -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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+34
View File
@@ -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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+34
View File
@@ -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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

+34
View File
@@ -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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+34
View File
@@ -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
+3 -1
View File
@@ -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]
@@ -0,0 +1,4 @@
extends Resource
class_name RegisterQuestion
var txt : String
+79
View File
@@ -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)
@@ -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"]
+68
View File
@@ -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")
+101
View File
@@ -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"]
@@ -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()
@@ -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"
@@ -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
@@ -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"
@@ -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