Files
skelly/scenes/ui/SettingsMenu.gd
Vladimir nett00n Budylnikov 3b8da89ad5
Some checks failed
Continuous Integration / Code Formatting (push) Successful in 33s
Continuous Integration / Code Quality Check (push) Successful in 29s
Continuous Integration / Test Execution (push) Failing after 16s
Continuous Integration / CI Summary (push) Failing after 4s
more lint and formatting
2025-10-01 15:04:40 +04:00

375 lines
11 KiB
GDScript

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")