Fix identity dialog layout reparenting (#130)

This commit is contained in:
Alexander Sellite 2026-09-02 09:51:46 -04:00
parent 0e42137552
commit 44ccd1da75
4 changed files with 139 additions and 1 deletions

View file

@ -21,6 +21,7 @@ readonly -a QUICK_TESTS=(
"tests/controller_mapping_validation.gd"
"tests/controller_world_interaction_validation.gd"
"tests/controller_ui_navigation_validation.gd"
"tests/data_management_dialog_validation.gd"
"tests/dedicated_server_config_validation.gd"
"tests/discovery_privacy_validation.gd"
"tests/digging_prototype_validation.gd"

View file

@ -0,0 +1,124 @@
extends SceneTree
const SettingsPanelScene: PackedScene = preload(
"res://ui/settings_panel.tscn"
)
const EXPORT_SIZE := Vector2(600.0, 385.0)
const IMPORT_SIZE := Vector2(600.0, 310.0)
func _initialize() -> void:
_run.call_deferred()
func _run() -> void:
root.size = Vector2i(1280, 720)
await _validate_identity_dialog_lifecycle()
print("Data management dialog validation: PASS")
quit(0)
func _validate_identity_dialog_lifecycle() -> void:
var stage := Control.new()
stage.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
root.add_child(stage)
var settings := SettingsPanelScene.instantiate() as Control
settings.set_anchors_preset(Control.PRESET_CENTER)
settings.position = Vector2(280.0, 60.0)
settings.size = Vector2(720.0, 600.0)
stage.add_child(settings)
for _frame: int in 3:
await process_frame
var overlay := settings.get("_data_management_dialog") as Control
assert(overlay != null)
assert(overlay.get_parent() == stage)
assert(overlay.position.is_equal_approx(Vector2.ZERO))
assert(overlay.size.is_equal_approx(Vector2(root.size)))
assert(overlay.scale.is_equal_approx(Vector2.ONE))
settings.set("_pending_identity_type", "player")
settings.call(
"_identity_export_file_selected", "/tmp/player-identity.nfidentity"
)
await _wait_for_layout()
var first_fields := overlay.find_child(
"PassphraseFields", true, false
) as VBoxContainer
assert(first_fields != null)
var first_layout: Dictionary = _capture_layout(overlay, first_fields)
_assert_dialog_layout(overlay, first_fields, EXPORT_SIZE)
overlay.call("dismiss")
settings.set("_pending_identity_type", "host")
settings.call(
"_identity_import_file_selected", "/tmp/host-identity.nfidentity"
)
await _wait_for_layout()
var import_fields := overlay.find_child(
"PassphraseFields", true, false
) as VBoxContainer
assert(import_fields != null)
_assert_dialog_layout(overlay, import_fields, IMPORT_SIZE)
assert(import_fields.find_child("ConfirmPassphrase", true, false) == null)
overlay.call("dismiss")
settings.call(
"_identity_export_file_selected", "/tmp/host-identity.nfidentity"
)
await _wait_for_layout()
var second_fields := overlay.find_child(
"PassphraseFields", true, false
) as VBoxContainer
assert(second_fields != null)
_assert_dialog_layout(overlay, second_fields, EXPORT_SIZE)
var second_layout: Dictionary = _capture_layout(overlay, second_fields)
assert(first_layout["panel_size"] == second_layout["panel_size"])
assert(first_layout["field_offsets"] == second_layout["field_offsets"])
settings.queue_free()
for _frame: int in 2:
await process_frame
stage.queue_free()
await process_frame
func _wait_for_layout() -> void:
for _frame: int in 3:
await process_frame
func _capture_layout(
dialog: Control,
fields: VBoxContainer,
) -> Dictionary:
var panel := dialog.get("_panel") as PanelContainer
var offsets: Array[float] = []
for child: Control in fields.get_children():
offsets.append(child.global_position.y - panel.global_position.y)
return {
"panel_size": panel.size,
"field_offsets": offsets,
}
func _assert_dialog_layout(
dialog: Control,
fields: VBoxContainer,
expected_size: Vector2,
) -> void:
var panel := dialog.get("_panel") as PanelContainer
var title := dialog.get("_title_label") as Label
var body_scroll := dialog.get("_body_scroll") as ScrollContainer
var actions := dialog.get("_actions") as VBoxContainer
assert(dialog.visible)
assert(panel.size.is_equal_approx(expected_size))
assert(title.get_global_rect().end.y <= body_scroll.get_global_rect().position.y)
assert(body_scroll.get_global_rect().end.y <= actions.get_global_rect().position.y)
var previous_bottom: float = -INF
for child: Control in fields.get_children():
var rect: Rect2 = child.get_global_rect()
assert(rect.position.y >= previous_bottom)
assert(rect.position.x >= panel.global_position.x)
assert(rect.end.x <= panel.get_global_rect().end.x)
previous_bottom = rect.end.y

View file

@ -0,0 +1 @@
uid://dqwhgxa41psp8

View file

@ -230,7 +230,19 @@ func _move_data_management_dialog_to_host(dialog_host: Control) -> void:
or _data_management_dialog.get_parent() == dialog_host
):
return
_data_management_dialog.reparent(dialog_host)
# The overlay is first added below SettingsPanel so it can finish _ready().
# Do not preserve that temporary parent's transform when moving it onto the
# canonical title/pause stage: doing so leaves the full-rect anchors paired
# with the settings panel's offset and makes the first presentation differ
# from later container layout passes.
_data_management_dialog.reparent(dialog_host, false)
_data_management_dialog.set_anchors_and_offsets_preset(
Control.PRESET_FULL_RECT
)
_data_management_dialog.position = Vector2.ZERO
_data_management_dialog.scale = Vector2.ONE
_data_management_dialog.rotation = 0.0
_data_management_dialog.pivot_offset = Vector2.ZERO
func _populate_option_buttons() -> void: