Guard destructive settings and radio actions
Full build / Import + headless tests (push) Successful in 36s
Full build / Full desktop build + optional release (push) Skipped

- Route reset campaign and tutorial replay through one reusable confirmation dialog; repeated presses cannot stack or emit twice.
- Keep emergency hold-to-send single-shot and cover the post-submit release.
- Add destructive-action regression checks, strings and documentation.
This commit is contained in:
Gig Line Dev committed 2026-11-02 16:29:26 +01:00
1 parent f6aa26e6ca
commit 7315166361
10 files changed
+121 -14

No files matched your search

+37 -5
View File
@@ -32,6 +32,7 @@ var _reduce_motion: CheckButton = null
var _captions: CheckButton = null
var _bind_buttons := {}
var _listening_action := ""
var _destructive_dialog: ConfirmationDialog = null
var settings: Dictionary = {}
func _ready() -> void:
@@ -136,7 +137,7 @@ func _build() -> void:
var replay := Button.new()
replay.name = "ReplayTutorialButton"
replay.text = Strings.lookup("ui.settings_replay")
replay.pressed.connect(func(): replay_tutorial_requested.emit())
replay.pressed.connect(_confirm_replay_tutorial)
UiStyles.apply_button(replay, 16)
replay_row.add_child(replay)
var reset_campaign := Button.new()
@@ -284,15 +285,46 @@ func _on_difficulty_selected(index: int) -> void:
settings["ai_difficulty"] = ["easy", "normal", "hard"][clampi(index, 0, 2)]
func _confirm_reset_campaign() -> void:
_open_destructive_dialog(Strings.lookup("ui.settings_reset_title"),
Strings.lookup("ui.settings_reset_confirm"),
func(): reset_campaign_requested.emit())
func _confirm_replay_tutorial() -> void:
_open_destructive_dialog(Strings.lookup("ui.settings_replay_title"),
Strings.lookup("ui.settings_replay_confirm"),
func(): replay_tutorial_requested.emit())
## One modal at a time; a second press while it is open focuses the existing
## dialog instead of creating another one (and possibly emitting twice).
func _open_destructive_dialog(title: String, body: String, callback: Callable) -> void:
if _destructive_dialog != null and is_instance_valid(_destructive_dialog):
# Reuse the open confirmation instead of stacking a second one. The
# dialog is dismissed on confirm/cancel, so an existing instance is
# always the one waiting for the player's decision.
if _destructive_dialog.is_inside_tree():
_destructive_dialog.grab_focus()
return
var dialog := ConfirmationDialog.new()
dialog.title = "Reset campaign"
dialog.dialog_text = Strings.lookup("ui.settings_reset_confirm")
dialog.confirmed.connect(func(): reset_campaign_requested.emit())
_destructive_dialog = dialog
dialog.title = title
dialog.dialog_text = body
dialog.confirmed.connect(func():
if _destructive_dialog != dialog:
return
_dismiss_destructive_dialog(dialog)
callback.call())
dialog.canceled.connect(func(): _dismiss_destructive_dialog(dialog))
add_child(dialog)
UiStyles.apply_button(dialog.get_ok_button(), 16)
UiStyles.apply_button(dialog.get_cancel_button(), 16)
dialog.popup_centered()
dialog.confirmed.connect(dialog.queue_free)
func _dismiss_destructive_dialog(dialog: ConfirmationDialog) -> void:
if _destructive_dialog == dialog:
_destructive_dialog = null
if is_instance_valid(dialog):
dialog.hide()
dialog.queue_free()
func _refresh_bind_buttons() -> void:
var remaps: Dictionary = settings.get("input_bindings", {})