Added menu settings add-on

This commit is contained in:
2026-03-07 14:16:44 +01:00
parent 8b7a8e014f
commit df8c8c6c3b
70 changed files with 4053 additions and 1 deletions

View File

@@ -0,0 +1,38 @@
[gd_scene load_steps=2 format=3 uid="uid://bbtri7oyxvehi"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_jjel1"]
[node name="InputButton" type="PanelContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="ActionLabel" type="Label" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 2
size_flags_vertical = 1
mouse_filter = 1
text = "action name"
horizontal_alignment = 1
vertical_alignment = 1
[node name="ActionInput" type="Button" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
mouse_filter = 1
theme_override_styles/focus = SubResource("StyleBoxEmpty_jjel1")
text = "input key"

View File

@@ -0,0 +1,305 @@
[gd_scene format=3 uid="uid://cqqhgk8ufkmqa"]
[ext_resource type="PackedScene" uid="uid://bbtri7oyxvehi" path="res://addons/modular-settings-menu/scenes/settings-elements/controls-elements/input-settings-panel/input-settings-panel-elements/input_button.tscn" id="1_1y7ch"]
[sub_resource type="GDScript" id="GDScript_0eq0v"]
resource_name = "input_settings_panel"
script/source = "extends Control
# Button scene for actions
@export var InputButtonScene: PackedScene
# Panel node references
@onready var ActionListRef: VBoxContainer = %ActionList
@onready var ResetButtonRef: Button = %ResetButton
@onready var BackButtonRef: Button = $VBoxContainer/HBoxContainer/BackButton
@onready var ApplyButtonRef: Button = $VBoxContainer/HBoxContainer/ApplyButton
# List of events for each action
var actionEvents_: Dictionary
# Cache for changed actions
var panelCache_: Dictionary
# Reference list to the button for each action
var actionReferenceList_: Dictionary
# Reference to the element this panel belongs to
var PanelOwnerRef: ButtonElement
# For remapping
var isRemapping: bool = false
var actionToRemap: String
var ActionInputButtonRef: Button
func _ready():
# Connect necessary signals
ResetButtonRef.connect(\"pressed\", on_reset_button_pressed)
BackButtonRef.connect(\"pressed\", on_back_button_pressed)
ApplyButtonRef.connect(\"pressed\", on_apply_button_pressed)
# Called to populate the action list with actions
func create_action_list(reset: bool = false) -> void:
# All the remappable actions
var INPUT_ACTIONS_: Dictionary = PanelOwnerRef.ACTION_LIST_.duplicate()
INPUT_ACTIONS_.make_read_only()
# Get the events for the actions
actionEvents_ = get_events(INPUT_ACTIONS_, reset)
# Check if the action list is already populated
if ActionListRef.get_child_count() > 0:
# Remove all the existing actions from the action list
for child in ActionListRef.get_children():
child.queue_free()
# Create the actions for the action list
for action in INPUT_ACTIONS_:
# Instantiate the button scene
var ButtonRef: PanelContainer = InputButtonScene.instantiate()
# Get references to the elements of the button scene
var ActionLabelRef: Label = ButtonRef.find_child(\"ActionLabel\")
var ActionInputRef: Button = ButtonRef.find_child(\"ActionInput\")
# Get the event for the action
var event = actionEvents_[action]
# Add a reference of the instantiated button to the reference list
actionReferenceList_[action] = ButtonRef
# Change the label text of the button to the action it corresponds to
ActionLabelRef.set_text(INPUT_ACTIONS_[action])
ActionInputRef.set_text(event.as_text())
ActionListRef.add_child(ButtonRef)
ActionInputRef.connect(
\"pressed\",
on_input_button_pressed.bind(action, ActionInputRef)
)
func on_input_button_pressed(action: String, ActionInputRef: Button) -> void:
# Check if a button is not being remapped currently
if not isRemapping:
isRemapping = true
actionToRemap = action
ActionInputButtonRef = ActionInputRef
ActionInputRef.set_text(\"Press key to bind...\")
func _input(event: InputEvent):
# Check if an action is being remapped currently
if isRemapping:
# Check if the desired input is either a keyboard or mouse input
if (
event is InputEventKey
or event is InputEventMouseButton
and event.pressed
):
# Check if the event is a mouse event and if it was a double click
if event is InputEventMouseButton and event.double_click:
# Disable the double click flag
event.double_click = false
# Set the input button's text to the inputted event
ActionInputButtonRef.set_text(event.as_text())
# Check if the inputted event is different compared to the saved one for the action
if not check_matching_event(event):
var duplicateEvent: String = check_duplicate_event(event)
actionEvents_[actionToRemap] = event
panelCache_[actionToRemap] = event
ApplyButtonRef.set_disabled(false)
if duplicateEvent:
var ActionInputRef: Button =\\
actionReferenceList_[duplicateEvent].find_child(
\"ActionInput\"
)
actionEvents_[duplicateEvent] = InputEventKey.new()
panelCache_[duplicateEvent] = actionEvents_[duplicateEvent]
ActionInputRef.set_text(
actionEvents_[duplicateEvent].as_text()
)
isRemapping = false
actionToRemap = \"\"
ActionInputButtonRef = null
accept_event()
# Checks if the inputted event matches the one saved for the action
func check_matching_event(event: InputEvent) -> bool:
var eventType: String = event.get_class()
var eventButton: int = get_event_button(event)
var currentEvent = actionEvents_[actionToRemap]
# Check if the event matches the action to be remapped
if (
currentEvent.is_class(eventType)
and get_event_button(currentEvent) == eventButton
):
return true
return false
# Checks if any other action has the inputted event as it's event
func check_duplicate_event(event: InputEvent) -> String:
var eventType: String = event.get_class()
var eventButton: int = get_event_button(event)
# Check for duplicates in other actions
for action in actionEvents_:
# Check if the action is not the one being remapped
if action != actionToRemap:
var storedEvent = actionEvents_[action]
# Check if the input type and event matches the inputted event
if (
storedEvent.is_class(eventType)
and get_event_button(storedEvent) == eventButton
):
return action
return \"\"
# Called to get the physical index for the inputted event depending on the input event's type
func get_event_button(event: InputEvent) -> int:
# Check for the input event's type
match event.get_class():
\"InputEventKey\":
return event.get_keycode()
\"InputEventMouseButton\":
return event.get_button_index()
return -1
# Called to update the action's events to the cached events
func update_action_events(actionList_: Dictionary) -> void:
# Itterate through all the recieved actions
for action in actionList_:
# Remove all events for the action
InputMap.action_erase_events(action)
# Check if the action has an event assigned to it
if actionList_[action]:
# Add the new event to the action
InputMap.action_add_event(action, actionList_[action])
# Called to get all the events for the remappable actions
func get_events(actions_: Dictionary, reset: bool = false) -> Dictionary:
var events_: Dictionary = {}
if PanelOwnerRef.noSaveFile or PanelOwnerRef.invalidSaveFile or reset:
InputMap.load_from_project_settings()
for action in actions_:
# Retrieve the first event for the action
events_[action] = InputMap.action_get_events(action)[0]
panelCache_ = events_.duplicate(true)
else:
# Retrieve the events from the loaded data
events_ = PanelOwnerRef.inputSettingsData_.duplicate()
return events_
func on_reset_button_pressed():
panelCache_.clear()
create_action_list(true)
ApplyButtonRef.set_disabled(false)
func on_back_button_pressed():
# Check if there have been any changes made
if ApplyButtonRef.is_disabled():
# Clear the cache and return normally
panelCache_.clear()
hide()
owner.SettingsPanelRef.show()
else:
# Display the discard changes popup
owner.display_discard_changes(self)
func on_apply_button_pressed():
# Update the input map
update_action_events(panelCache_)
panelCache_.clear()
# Send the new data to the parent element
PanelOwnerRef.panel_settings_changed(actionEvents_)
ApplyButtonRef.set_disabled(true)
"
[node name="InputSettingsPanel" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = SubResource("GDScript_0eq0v")
InputButtonScene = ExtResource("1_1y7ch")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -194.0
offset_top = -29.0
offset_right = 194.0
offset_bottom = 30.0
grow_horizontal = 2
grow_vertical = 2
[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"]
custom_minimum_size = Vector2(0, 280)
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/PanelContainer"]
layout_mode = 2
theme_override_constants/margin_left = 8
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 8
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/PanelContainer/MarginContainer"]
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="ActionList" type="VBoxContainer" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="ResetButton" type="Button" parent="VBoxContainer/PanelContainer/MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
focus_mode = 0
text = "Reset to default"
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="ApplyButton" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_direction = 1
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
disabled = true
text = "Apply"
[node name="BackButton" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_direction = 1
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
text = "Back"

View File

@@ -0,0 +1,165 @@
[gd_scene format=3 uid="uid://i0w5gftb0j16"]
[ext_resource type="PackedScene" uid="uid://cqqhgk8ufkmqa" path="res://addons/modular-settings-menu/scenes/settings-elements/controls-elements/input-settings-panel/input_settings_panel.tscn" id="1_6pc66"]
[sub_resource type="GDScript" id="GDScript_te2ja"]
resource_name = "input_settings"
script/source = "extends ButtonElement
##List of actions to make remappable and how the name they should display.
@export var ACTION_LIST_: Dictionary = {
\"ui_up\": \"Up\",
\"ui_down\": \"Down\",
\"ui_left\": \"Left\",
\"ui_right\": \"Right\",
}
# Path to the settings save file
var DATA_FOLDER: String = SettingsDataManager.DATA_FOLDER
const FILE_NAME: String = \"keybinds\"
const FILE_EXTENSION: String = \".json\"
var PATH: String = DATA_FOLDER + \"/\" + FILE_NAME + FILE_EXTENSION
# Currently stored input data
var inputSettingsData_: Dictionary
# Flag for checking if a save file exists for input settings
var noSaveFile: bool
# Flag for checking if an invalid value was found in the save file
var invalidSaveFile: bool = false
func _ready():
super._ready()
ACTION_LIST_.make_read_only()
SettingsDataManager.connect(\"settings_retrieved\", load_settings)
# Verify the directory
DirAccess.make_dir_absolute(DATA_FOLDER)
# Check if a save file exists
if not FileAccess.file_exists(PATH):
noSaveFile = true
# Loads the saved/default values of the element
func load_settings() -> void:
# Get the current value for the element
if noSaveFile:
# Get the events for the remappable actions from the project settings
inputSettingsData_ = ElementPanelRef.get_events(ACTION_LIST_)
save_input_settings()
else:
get_input_settings()
# Check if save file is valid
if invalidSaveFile:
# Get the events for the remappable actions from the project settings
inputSettingsData_ = ElementPanelRef.get_events(ACTION_LIST_)
save_input_settings()
else:
call_deferred(\"apply_settings\")
func pressed() -> void:
# Check if a save file exists
if not FileAccess.file_exists(PATH):
noSaveFile = true
ElementPanelRef.create_action_list()
# Switch panels
ParentRef.SettingsMenuRef.SettingsPanelRef.hide()
ElementPanelRef.show()
# Called to apply the settings in the settings cache
func apply_settings() -> void:
# Assign the events for the remappable actions
ElementPanelRef.update_action_events(inputSettingsData_)
# Called to update the input settings data
func panel_settings_changed(newValue_: Dictionary) -> void:
inputSettingsData_ = newValue_.duplicate()
save_input_settings()
# Called to save the input settings
func save_input_settings() -> void:
var file := FileAccess.open(PATH, FileAccess.WRITE)
var data_: Dictionary = {}
# Itterate through the input settings
for input in inputSettingsData_:
var inputEvent = inputSettingsData_[input]
# Serialize the InputEvent of the action
match inputEvent.get_class():
\"InputEventKey\":
data_[input] = {
\"type\": \"keyboard\",
\"key\": inputEvent.get_keycode()
}
\"InputEventMouseButton\":
data_[input] = {
\"type\": \"mouse\",
\"button\": inputEvent.get_button_index()
}
# Save the string to a json file
file.store_string(JSON.stringify(data_, \"\\t\", false))
file.close()
noSaveFile = false
# Called to retrieve the input settings data from the save file
func get_input_settings() -> void:
var file := FileAccess.open(PATH, FileAccess.READ)
# Parse the json string to a dictionary
var data_ = JSON.parse_string(file.get_as_text())
file.close()
# Check if there were any errors or if the save file is empty
if data_ == null or data_.is_empty():
push_error(\"Failed to parse input settings\")
invalidSaveFile = true
return
verify_settings_data(data_)
# Used for verifying the integrity of the save file
func verify_settings_data(data_: Dictionary) -> void:
# Itterate through all the retrieved data
for input in data_:
# Check if the action exists
if not ACTION_LIST_.has(input):
push_warning(\"Invalid input settings entry: \", input)
invalidSaveFile = true
continue
# Reconstruct the InputEvent for the action
match data_[input][\"type\"]:
\"keyboard\":
var key := InputEventKey.new()
key.set_keycode(data_[input][\"key\"])
inputSettingsData_[input] = key
\"mouse\":
var button := InputEventMouseButton.new()
button.set_button_index(data_[input][\"button\"])
inputSettingsData_[input] = button
# Itterate through all the expected actions
for action in ACTION_LIST_:
if not data_.has(action):
data_[action] = InputMap.action_get_events(action)[0]
invalidSaveFile = true
push_warning(\"Input action is missing: \", action)
"
[node name="InputSettings" type="Button"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
text = "Input Settings"
script = SubResource("GDScript_te2ja")
ElementPanelScene = ExtResource("1_6pc66")

View File

@@ -0,0 +1,37 @@
[gd_scene load_steps=3 format=3 uid="uid://c2tgt7fu0n5wp"]
[sub_resource type="GDScript" id="GDScript_te2ja"]
resource_name = "invert_y"
script/source = "extends ToggleElement
# Called to apply the settings in the settings cache
func _apply_settings() -> void:
apply_in_game_setting(currentValue)
"
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_drjjf"]
[node name="InvertY" type="HBoxContainer" node_paths=PackedStringArray("ToggleRef")]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = SubResource("GDScript_te2ja")
ToggleRef = NodePath("Toggle")
IDENTIFIER = "InvertY"
IS_IN_GAME_SETTING = true
[node name="Label" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
text = "Invert Y-Axis"
vertical_alignment = 1
[node name="Toggle" type="CheckButton" parent="."]
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
theme_override_styles/focus = SubResource("StyleBoxEmpty_drjjf")

View File

@@ -0,0 +1,60 @@
[gd_scene load_steps=4 format=3 uid="uid://cy8n6yalxprb7"]
[sub_resource type="GDScript" id="GDScript_s1j2c"]
resource_name = "sensitivity"
script/source = "extends SliderElement
# Element specific script for applying its value to the game
func _apply_settings() -> void:
apply_in_game_setting(currentValue)
"
[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_hlot4"]
size = Vector2(0, 0)
[sub_resource type="Theme" id="Theme_4i2xw"]
SpinBox/icons/updown = SubResource("PlaceholderTexture2D_hlot4")
[node name="Sensitivity" type="HBoxContainer" node_paths=PackedStringArray("SliderRef", "ValueBoxRef")]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = SubResource("GDScript_s1j2c")
MAX_VALUE = 5.0
STEP_VALUE = 0.01
SliderRef = NodePath("SliderValue/Slider")
ValueBoxRef = NodePath("SliderValue/Value")
IDENTIFIER = "Sensitivity"
IS_IN_GAME_SETTING = true
[node name="Label" type="Label" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
text = "Mouse Sensitivity"
vertical_alignment = 1
[node name="SliderValue" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 6
[node name="Slider" type="HSlider" parent="SliderValue"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 1
max_value = 0.0
step = 0.0
ticks_on_borders = true
[node name="Value" type="SpinBox" parent="SliderValue"]
layout_mode = 2
size_flags_horizontal = 8
size_flags_vertical = 4
theme = SubResource("Theme_4i2xw")
max_value = 0.0
step = 0.0
alignment = 2