extends Control signal back_to_main_menu @export var settings_manager: Node = SettingsManager @export var localization_manager: Node = LocalizationManager # Progress reset confirmation dialog var confirmation_dialog: AcceptDialog # Navigation system variables var navigable_controls: Array[Control] = [] var current_control_index: int = 0 var original_control_scales: Array[Vector2] = [] var original_control_modulates: Array[Color] = [] @onready var master_slider = $SettingsContainer/MasterVolumeContainer/MasterVolumeSlider @onready var music_slider = $SettingsContainer/MusicVolumeContainer/MusicVolumeSlider @onready var sfx_slider = $SettingsContainer/SFXVolumeContainer/SFXVolumeSlider @onready var language_stepper = $SettingsContainer/LanguageContainer/LanguageStepper @onready var reset_progress_button = $ResetSettingsContainer/ResetProgressButton func _ready() -> void: add_to_group("localizable") DebugManager.log_info("SettingsMenu ready", "Settings") # Language selector is initialized automatically var master_callback: Callable = _on_volume_slider_changed.bind( "master_volume" ) if not master_slider.value_changed.is_connected(master_callback): master_slider.value_changed.connect(master_callback) var music_callback: Callable = _on_volume_slider_changed.bind( "music_volume" ) if not music_slider.value_changed.is_connected(music_callback): music_slider.value_changed.connect(music_callback) var sfx_callback: Callable = _on_volume_slider_changed.bind("sfx_volume") if not sfx_slider.value_changed.is_connected(sfx_callback): sfx_slider.value_changed.connect(sfx_callback) # Language stepper connections are handled in the .tscn file _update_controls_from_settings() update_text() _setup_navigation_system() _setup_confirmation_dialog() func _update_controls_from_settings() -> void: master_slider.value = settings_manager.get_setting("master_volume") music_slider.value = settings_manager.get_setting("music_volume") sfx_slider.value = settings_manager.get_setting("sfx_volume") # Language display is handled by the ValueStepper component func _on_volume_slider_changed(value: float, setting_key: String) -> void: # Input validation for volume settings if not setting_key in ["master_volume", "music_volume", "sfx_volume"]: DebugManager.log_error( "Invalid volume setting key: " + str(setting_key), "Settings" ) return if typeof(value) != TYPE_FLOAT and typeof(value) != TYPE_INT: DebugManager.log_error( "Invalid volume value type: " + str(typeof(value)), "Settings" ) return # Clamp value to valid range var clamped_value: float = clamp(float(value), 0.0, 1.0) if clamped_value != value: DebugManager.log_warn( "Volume value %f clamped to %f" % [value, clamped_value], "Settings" ) if not settings_manager.set_setting(setting_key, clamped_value): DebugManager.log_error( "Failed to set volume setting: " + setting_key, "Settings" ) func _exit_settings() -> void: DebugManager.log_info("Exiting settings", "Settings") settings_manager.save_settings() back_to_main_menu.emit() func _input(event: InputEvent) -> void: if ( event.is_action_pressed("action_east") or event.is_action_pressed("pause_menu") ): DebugManager.log_debug( "Cancel/back action pressed in settings", "Settings" ) _exit_settings() get_viewport().set_input_as_handled() return # Vertical navigation between controls if event.is_action_pressed("move_up"): _navigate_controls(-1) get_viewport().set_input_as_handled() elif event.is_action_pressed("move_down"): _navigate_controls(1) get_viewport().set_input_as_handled() # Horizontal navigation for current control elif event.is_action_pressed("move_left"): _adjust_current_control(-1) get_viewport().set_input_as_handled() elif event.is_action_pressed("move_right"): _adjust_current_control(1) get_viewport().set_input_as_handled() # Activate current control elif event.is_action_pressed("action_south"): _activate_current_control() get_viewport().set_input_as_handled() func _on_back_button_pressed() -> void: AudioManager.play_ui_click() DebugManager.log_info("Back button pressed", "Settings") _exit_settings() func update_text() -> void: $SettingsContainer/SettingsTitle.text = tr("settings_title") $SettingsContainer/MasterVolumeContainer/MasterVolume.text = tr( "master_volume" ) $SettingsContainer/MusicVolumeContainer/MusicVolume.text = tr( "music_volume" ) $SettingsContainer/SFXVolumeContainer/SFXVolume.text = tr("sfx_volume") $SettingsContainer/LanguageContainer/LanguageLabel.text = tr("language") $BackButtonContainer/BackButton.text = tr("back") reset_progress_button.text = tr("reset_progress") func _on_reset_setting_button_pressed() -> void: AudioManager.play_ui_click() DebugManager.log_info("Resetting settings", "Settings") settings_manager.reset_settings_to_defaults() _update_controls_from_settings() localization_manager.change_language( settings_manager.get_setting("language") ) func _setup_navigation_system() -> void: navigable_controls.clear() original_control_scales.clear() original_control_modulates.clear() # Add controls in navigation order navigable_controls.append(master_slider) navigable_controls.append(music_slider) navigable_controls.append(sfx_slider) navigable_controls.append(language_stepper) # Use the ValueStepper component navigable_controls.append($BackButtonContainer/BackButton) navigable_controls.append($ResetSettingsContainer/ResetSettingButton) navigable_controls.append(reset_progress_button) # Store original visual properties for control in navigable_controls: original_control_scales.append(control.scale) original_control_modulates.append(control.modulate) _update_visual_selection() func _navigate_controls(direction: int) -> void: AudioManager.play_ui_click() current_control_index = ( (current_control_index + direction) % navigable_controls.size() ) if current_control_index < 0: current_control_index = navigable_controls.size() - 1 _update_visual_selection() DebugManager.log_info( "Settings navigation: index " + str(current_control_index), "Settings" ) func _adjust_current_control(direction: int) -> void: if ( current_control_index < 0 or current_control_index >= navigable_controls.size() ): return var control: Control = navigable_controls[current_control_index] # Handle sliders if control is HSlider: var slider: HSlider = control as HSlider var step: float = slider.step if slider.step > 0 else 0.1 var new_value: float = slider.value + (direction * step) new_value = clamp(new_value, slider.min_value, slider.max_value) slider.value = new_value AudioManager.play_ui_click() DebugManager.log_info( ( "Slider adjusted: %s = %f" % [_get_control_name(control), new_value] ), "Settings" ) # Handle language stepper with left/right elif control == language_stepper: if language_stepper.handle_input_action( "move_left" if direction == -1 else "move_right" ): AudioManager.play_ui_click() func _activate_current_control() -> void: if ( current_control_index < 0 or current_control_index >= navigable_controls.size() ): return var control: Control = navigable_controls[current_control_index] # Handle buttons if control is Button: AudioManager.play_ui_click() DebugManager.log_info( "Activating button via keyboard/gamepad: " + control.text, "Settings" ) control.pressed.emit() # Handle language stepper (no action needed on activation, left/right handles it) elif control == language_stepper: DebugManager.log_info( "Language stepper selected - use left/right to change", "Settings" ) func _update_visual_selection() -> void: for i in range(navigable_controls.size()): var control = navigable_controls[i] if i == current_control_index: # Use the ValueStepper's built-in highlighting if control == language_stepper: language_stepper.set_highlighted(true) else: control.scale = ( original_control_scales[i] * UIConstants.UI_CONTROL_HIGHLIGHT_SCALE ) control.modulate = Color(1.1, 1.1, 0.9) else: # Reset highlighting if control == language_stepper: language_stepper.set_highlighted(false) else: control.scale = original_control_scales[i] control.modulate = original_control_modulates[i] func _get_control_name(control: Control) -> String: if control == master_slider: return "master_volume" if control == music_slider: return "music_volume" if control == sfx_slider: return "sfx_volume" if control == language_stepper: return language_stepper.get_control_name() return "button" func _on_language_stepper_value_changed( new_value: String, new_index: float ) -> void: DebugManager.log_info( ( "Language changed via ValueStepper: " + new_value + " (index: " + str(int(new_index)) + ")" ), "Settings" ) func _setup_confirmation_dialog() -> void: """Create confirmation dialog for progress reset""" confirmation_dialog = AcceptDialog.new() confirmation_dialog.title = tr("confirm_reset_title") confirmation_dialog.dialog_text = tr("confirm_reset_message") confirmation_dialog.ok_button_text = tr("reset_confirm") confirmation_dialog.add_cancel_button(tr("cancel")) # Make dialog modal and centered confirmation_dialog.set_flag(Window.FLAG_POPUP, true) confirmation_dialog.popup_window = true # Connect signals confirmation_dialog.confirmed.connect(_on_reset_progress_confirmed) confirmation_dialog.canceled.connect(_on_reset_progress_canceled) add_child(confirmation_dialog) func _on_reset_progress_button_pressed() -> void: """Handle reset progress button press with confirmation""" AudioManager.play_ui_click() DebugManager.log_info("Reset progress button pressed", "Settings") # Update dialog text with current translations confirmation_dialog.title = tr("confirm_reset_title") confirmation_dialog.dialog_text = tr("confirm_reset_message") confirmation_dialog.ok_button_text = tr("reset_confirm") # Show confirmation dialog confirmation_dialog.popup_centered() func _on_reset_progress_confirmed() -> void: """Actually reset the progress after confirmation""" AudioManager.play_ui_click() DebugManager.log_info("Progress reset confirmed by user", "Settings") # Call SaveManager to reset all progress if SaveManager.reset_all_progress(): DebugManager.log_info("All progress successfully reset", "Settings") # Show success message var success_dialog: AcceptDialog = AcceptDialog.new() success_dialog.title = tr("reset_success_title") success_dialog.dialog_text = tr("reset_success_message") success_dialog.ok_button_text = tr("ok") add_child(success_dialog) success_dialog.popup_centered() # Auto-close success dialog and remove it after 3 seconds success_dialog.confirmed.connect(func(): success_dialog.queue_free()) await get_tree().create_timer(3.0).timeout if is_instance_valid(success_dialog): success_dialog.queue_free() else: DebugManager.log_error("Failed to reset progress", "Settings") # Show error message var error_dialog: AcceptDialog = AcceptDialog.new() error_dialog.title = tr("reset_error_title") error_dialog.dialog_text = tr("reset_error_message") error_dialog.ok_button_text = tr("ok") add_child(error_dialog) error_dialog.popup_centered() error_dialog.confirmed.connect(func(): error_dialog.queue_free()) func _on_reset_progress_canceled() -> void: """Handle reset progress cancellation""" AudioManager.play_ui_click() DebugManager.log_info("Progress reset canceled by user", "Settings")