extends TabContainer @onready var action_remapping_button_scene: PackedScene = load("res://ui/components/settings-menu/action-remapping-button/action_remapping_button.tscn") func _ready() -> void: call_deferred("_populate_menu") func _populate_menu() -> void: var tab_idx = 0 # Iterate through root categories (these become tabs) for root_category in Settings.get_root_categories(): var margin_container = MarginContainer.new() add_child(margin_container) set_tab_title(tab_idx, root_category.display_text) var scroll_container = ScrollContainer.new() scroll_container.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED margin_container.add_child(scroll_container) var margin_container2 = MarginContainer.new() margin_container2.size_flags_horizontal = Control.SIZE_EXPAND_FILL margin_container2.size_flags_vertical = Control.SIZE_EXPAND_FILL scroll_container.add_child(margin_container2) var cat_vbox = VBoxContainer.new() cat_vbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL cat_vbox.size_flags_vertical = Control.SIZE_EXPAND_FILL margin_container2.add_child(cat_vbox) # Add subcategories as sections, and their settings _populate_category(cat_vbox, root_category, root_category.name) tab_idx += 1 ## Recursively populate a category and its subcategories ## Subcategories become section headers, deeper ones are flattened func _populate_category(container: VBoxContainer, category: Settings.SettingCategory, path_prefix: String, depth: int = 0) -> void: # If this is a subcategory (depth > 0), add a section header if depth > 0: var lbl_section_title = Label.new() lbl_section_title.text = category.display_text lbl_section_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER lbl_section_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL lbl_section_title.theme_type_variation = "SettingsGroupLabel" container.add_child(lbl_section_title) container.add_child(HSeparator.new()) # Add all settings in this category for setting in category.settings: _add_setting_ui(container, setting, path_prefix + "/" + setting.name) # Recursively add all subcategories for sub_category in category.sub_categories: var sub_path = path_prefix + "/" + sub_category.name _populate_category(container, sub_category, sub_path, depth + 1) func _add_setting_ui(container: VBoxContainer, setting: Settings.Setting, setting_path: String) -> void: var hbox = HBoxContainer.new() hbox.custom_minimum_size = Vector2(0, 40) container.add_child(hbox) # Another HBoxContainer for the label and revert button var hbox_lbl = HBoxContainer.new() hbox_lbl.size_flags_horizontal = Control.SIZE_EXPAND_FILL hbox.add_child(hbox_lbl) # Label for the name of the setting var lbl_name = Label.new() lbl_name.text = setting.display_text lbl_name.size_flags_horizontal = Control.SIZE_EXPAND_FILL hbox_lbl.add_child(lbl_name) # Revert button for changing the setting back to its default value # For input bindings a new button for each remapping is created later if not setting is Settings.InputRemappingSetting: var revert_btn = Button.new() revert_btn.icon = get_theme_icon("icon", "RevertButton") revert_btn.custom_minimum_size = Vector2(32, 32) revert_btn.theme_type_variation = "FlatButton" revert_btn.connect("pressed", func(): Settings.reset_value(setting_path)) hbox_lbl.add_child(revert_btn) setting.value_changed.connect(func(__): _update_revert_button(setting_path, revert_btn)) _update_revert_button(setting_path, revert_btn) # TODO: Tooltip # Setting-specific editor if setting is Settings.FloatSetting: var slider_setting = setting as Settings.FloatSetting var slider = HSlider.new() slider.min_value = slider_setting.min_value slider.max_value = slider_setting.max_value slider.step = slider_setting.step slider.value = slider_setting.value slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL slider.size_flags_vertical = Control.SIZE_FILL slider.connect("value_changed", setting.set_value) setting.value_changed.connect(slider.set_value_no_signal) hbox.add_child(slider) elif setting is Settings.OptionSetting: var dropdown_setting = setting as Settings.OptionSetting var option_button = OptionButton.new() for i in range(dropdown_setting.options.size()): option_button.add_item(dropdown_setting.options[i], i) # Find the index of the current value var current_idx = dropdown_setting.options.find(dropdown_setting.value) if current_idx >= 0: option_button.selected = current_idx option_button.connect("item_selected", func(idx: int): setting.set_value(dropdown_setting.options[idx])) setting.value_changed.connect(func(val): option_button.select(dropdown_setting.options.find(val))) hbox.add_child(option_button) elif setting is Settings.BoolSetting: var checkbox = CheckBox.new() checkbox.button_pressed = setting.value checkbox.connect("toggled", setting.set_value) setting.value_changed.connect(checkbox.set_pressed_no_signal) hbox.add_child(checkbox) elif setting is Settings.InputRemappingSetting: _add_input_remapping_ui(hbox, setting as Settings.InputRemappingSetting, setting_path) else: # Assume it's a basic boolean setting if it has a bool value if typeof(setting.value) == TYPE_BOOL: var checkbox = CheckBox.new() checkbox.button_pressed = setting.value checkbox.connect("toggled", setting.set_value) setting.value_changed.connect(checkbox.set_pressed_no_signal) hbox.add_child(checkbox) else: push_warning("Unknown setting type for: " + setting.display_text) func _add_input_remapping_ui(hbox: HBoxContainer, setting: Settings.InputRemappingSetting, setting_path: String) -> void: var events = setting.value as Array[InputEvent] # First reset button var input_revert_btn_1 = Button.new() input_revert_btn_1.icon = get_theme_icon("icon", "RevertButton") input_revert_btn_1.custom_minimum_size = Vector2(32, 32) input_revert_btn_1.theme_type_variation = "FlatButton" input_revert_btn_1.connect("pressed", func(): Settings.reset_input_binding(setting_path, 0)) hbox.add_child(input_revert_btn_1) setting.value_changed.connect(func(__): _update_input_mapping_revert_button(setting_path, 0, input_revert_btn_1)) _update_input_mapping_revert_button(setting_path, 0, input_revert_btn_1) # First remapping button var remapping_btn1 = action_remapping_button_scene.instantiate() remapping_btn1.set_event(events[0] if events.size() > 0 else null) remapping_btn1.connect("action_changed", func(event: InputEvent): _keymap_changed(event, 0, setting_path)) setting.value_changed.connect(func(value: Array[InputEvent]): _update_remapping_button(value, 0, remapping_btn1)) hbox.add_child(remapping_btn1) # Second reset button var input_revert_btn_2 = Button.new() input_revert_btn_2.icon = get_theme_icon("icon", "RevertButton") input_revert_btn_2.custom_minimum_size = Vector2(32, 32) input_revert_btn_2.theme_type_variation = "FlatButton" input_revert_btn_2.connect("pressed", func(): Settings.reset_input_binding(setting_path, 1)) hbox.add_child(input_revert_btn_2) setting.value_changed.connect(func(__): _update_input_mapping_revert_button(setting_path, 1, input_revert_btn_2)) _update_input_mapping_revert_button(setting_path, 1, input_revert_btn_2) # Second remapping button var remapping_btn2 = action_remapping_button_scene.instantiate() remapping_btn2.set_event(events[1] if events.size() > 1 else null) remapping_btn2.connect("action_changed", func(event: InputEvent): _keymap_changed(event, 1, setting_path)) setting.value_changed.connect(func(value: Array[InputEvent]): _update_remapping_button(value, 1, remapping_btn2)) hbox.add_child(remapping_btn2) func _keymap_changed(event: InputEvent, event_idx: int, setting_path: String) -> void: var events = Settings.get_value(setting_path).duplicate() if event_idx >= events.size(): events.push_back(null) events[event_idx] = event print("Keymap changed " + str(event_idx) + ":" + str(events)) Settings.set_value(setting_path, events) func _update_revert_button(p_setting_path: String, button: Button) -> void: button.visible = not Settings.is_value_default(p_setting_path) func _update_remapping_button(value: Array[InputEvent], index: int, btn: Button): btn.set_event(value[index] if value.size() > index else null) func _update_input_mapping_revert_button(p_setting_path: String, index: int, button: Button) -> void: button.modulate = Color.WHITE if not Settings.is_input_binding_default(p_setting_path, index) else Color.TRANSPARENT