135 lines
4.1 KiB
GDScript
135 lines
4.1 KiB
GDScript
@tool
|
|
extends Control
|
|
class_name FormValidator
|
|
|
|
signal control_validated(control: Control, passed: bool, messages: Array)
|
|
|
|
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[Control, Validator] = {}
|
|
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: ValidatorInfo in list:
|
|
if info.validator.skip_validation:
|
|
continue
|
|
var control: Control = info.control as Control
|
|
if control == null:
|
|
Logger.error("FormValidator: ValidatorInfo.control is not a Control: %s" % [info.control])
|
|
continue
|
|
var passed = info.validator.validate(control)
|
|
var messages = info.validator.get_messages()
|
|
if not passed:
|
|
_control_messages_map[control] = messages
|
|
control_validated.emit(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 controlKey in _control_validator_map.keys():
|
|
var control: Control = controlKey as Control
|
|
if control == null:
|
|
Logger.error("FormValidator: _control_validator_map key is not a Control")
|
|
continue
|
|
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)
|