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,40 @@
extends Button
class_name ButtonElement
## Reference to the element's panel scene.
@export var ElementPanelScene: PackedScene
## Reference to the node the settings element is under.
@onready var ParentRef: Node = owner
## Reference to the element's panel.
var ElementPanelRef: Node
func _ready():
# Connect necessary signals
connect("pressed", pressed)
create_element_panel()
func pressed() -> void:
# Switch panels
ParentRef.SettingsMenuRef.SettingsPanelRef.hide()
ElementPanelRef.show()
# Populate the settings cache of the panel
ElementPanelRef.get_settings()
## Called to create the element's panel.
func create_element_panel() -> void:
var ElementPanelsRef: Control = ParentRef.SettingsMenuRef.ElementPanelsRef
ElementPanelRef = ElementPanelScene.instantiate()
# Check if the element panel exists
if not ElementPanelsRef.find_child(ElementPanelRef.name):
# Give a reference of the element
ElementPanelRef.PanelOwnerRef = self
ElementPanelRef.hide()
# Add the panel to the element panels list
ElementPanelsRef.add_child(ElementPanelRef)
ElementPanelRef.set_owner(ParentRef.SettingsMenuRef)

View File

@@ -0,0 +1,67 @@
extends Control
class_name MultiElement
## A wrapper node for multi elements.
@export var MainElementRef: SettingsElement
@export var SUB_ELEMENTS_: Array[SettingsElement]
var ParentRef: SettingsSection
var currentValue :
get:
return MainElementRef.currentValue
func _enter_tree() -> void:
ParentRef = owner
ParentRef.connect("setting_changed", update_element)
ParentRef.connect("apply_button_pressed", apply_settings)
ParentRef.SettingsMenuRef.connect("changes_discarded", load_settings)
SettingsDataManager.connect("settings_retrieved", load_settings)
init_main_element()
init_sub_elements()
func init_main_element() -> void:
var elementId: String = MainElementRef.IDENTIFIER
MainElementRef.IS_MULTI_ELEMENT = true
MainElementRef.ParentRef = ParentRef
ParentRef.ELEMENT_REFERENCE_TABLE_[elementId] = MainElementRef
## Used to initialize sub elements of the multi element.
func init_sub_elements() -> void:
for ElementRef in SUB_ELEMENTS_:
ElementRef.IS_MULTI_ELEMENT = true
ElementRef.IS_SUB_ELEMENT = true
ElementRef.ParentRef = ParentRef
ParentRef.ELEMENT_REFERENCE_TABLE_[ElementRef.IDENTIFIER] = ElementRef
## Called when settings are loaded to display the appropriate elements.
func load_settings() -> void:
call_deferred("_display_sub_elements")
## Called when the main element's value changes do display the appropriate elements.
func update_element(elementId: String) -> void:
if elementId == MainElementRef.IDENTIFIER:
call_deferred("_display_sub_elements")
## Called to update the visibility of sub elements based on the main elements's current value.
## This function is overwritten by the multi element wrapper.
func _display_sub_elements() -> void:
return
## Called when the apply button is pressed.
func apply_settings() -> void:
if ParentRef.changedElements_.has(MainElementRef.IDENTIFIER):
for SubElementRef in SUB_ELEMENTS_:
if (
not SubElementRef.is_visible_in_tree()
or ParentRef.changedElements_.has(SubElementRef.IDENTIFIER)
):
continue
SubElementRef._apply_settings()

View File

@@ -0,0 +1,72 @@
extends SettingsElement
class_name OptionElement
## A settings element specifically for elements that have option buttons.
## Default value for the element.
## Value has to exist in OPTION_LIST_ otherwise the first option will be used.
@export var DEFAULT_VALUE: String
## Element node references
@export var OptionsRef: OptionButton
## List of options related to the settings element
var OPTION_LIST_
## Index of the currently selected item
var selectedIndex: int
func _ready() -> void:
super._ready()
OPTION_LIST_.make_read_only()
OptionsRef.connect("item_selected", option_selected)
func init_element() -> void:
fill_options_button()
func get_valid_values() -> Dictionary:
if not OPTION_LIST_.has(DEFAULT_VALUE):
push_warning("Invalid default value for element '" + IDENTIFIER + "'.")
if OPTION_LIST_ is Dictionary:
DEFAULT_VALUE = OPTION_LIST_.keys()[0]
else:
DEFAULT_VALUE = OPTION_LIST_[0]
return {
"defaultValue": DEFAULT_VALUE,
"validOptions": OPTION_LIST_
}
## Used to initialize the option button element.
func fill_options_button() -> void:
var index: int = 0
# Get the current item count of the option button
var itemCount: int = OptionsRef.get_item_count()
# Add the options from the received option list of the element
for option in OPTION_LIST_:
# Check if the option button has not been initialized yet
if itemCount == 0:
OptionsRef.add_item(option, index)
# Select the option that was loaded
if option == currentValue:
OptionsRef.select(index)
selectedIndex = index
index += 1
func option_selected(index: int) -> void:
# Check if the settings menu is open
if ParentRef.settingsCache_.size() > 0:
# Update the settings cache with the selected option
ParentRef.settingsCache_[IDENTIFIER] = OptionsRef.get_item_text(index)
# Check if the selected value is different than the saved value
ParentRef.settings_changed(IDENTIFIER)
# Update the element's values
currentValue = OptionsRef.get_item_text(index)
selectedIndex = index

View File

@@ -0,0 +1,205 @@
extends Control
class_name SettingsElement
## The base script for settings elements.
## Identifier for the element.
## This value is used as the key in the settings data.
@export var IDENTIFIER: String = "Element"
## Toggle based on whether the element handles a setting that requires an in game node to exist.
@export var IS_IN_GAME_SETTING: bool
## The name of the section the element is under.
@onready var SECTION: String = ParentRef.IDENTIFIER
## Reference to the section the settings element is under.
var ParentRef: SettingsSection
# Multi element flags
## Flag to turn an element into the main element of a multi element.
var IS_MULTI_ELEMENT: bool = false
## Flag to turn an element into a sub element of a multi element.
var IS_SUB_ELEMENT: bool = false
## Current value of the element.
var currentValue
func _enter_tree() -> void:
if not IS_MULTI_ELEMENT:
ParentRef = owner
func _ready() -> void:
SettingsDataManager.connect("settings_retrieved", load_settings)
# Check if the element is a sub element
if not IS_SUB_ELEMENT:
ParentRef.connect("apply_button_pressed", _apply_settings)
# Add an entry of the settings element to the section's reference table
ParentRef.ELEMENT_REFERENCE_TABLE_[IDENTIFIER] = self
## Used to initialize a settings element.
## This function is overwritten by each [b]type[/b] of element.
func init_element() -> void:
return
## Loads the saved or default value of the element.
func load_settings() -> void:
# List of valid values for the element
var VALUES_: Dictionary = get_valid_values()
VALUES_.make_read_only()
# Check if no save file exists
if SettingsDataManager.noSaveFile:
# Assign default value as current value
currentValue = VALUES_["defaultValue"]
# Add default value of element to the settings data
SettingsDataManager.settingsData_[SECTION][IDENTIFIER] = currentValue
else:
# Verify the existance and validity of the element in the settings data
if verify_settings_data(VALUES_):
# Get the current value from the settings data
currentValue = SettingsDataManager.settingsData_[SECTION][IDENTIFIER]
else:
# Assign default value as current value
currentValue = VALUES_["defaultValue"]
# Add default value of the element to the settings data
SettingsDataManager.settingsData_[SECTION][IDENTIFIER] = currentValue
SettingsDataManager.invalidSaveFile = true
init_element()
# Check if the current element is in an in game menu or if it is a sub element
if (
ParentRef.SettingsMenuRef.IS_IN_GAME_MENU == IS_IN_GAME_SETTING
or not IS_SUB_ELEMENT
):
# Apply the loaded values to the game
call_deferred("_apply_settings")
## Used to get the valid values an element can have for validating settings data.
## This function is overwritten by each [b]type[/b] of element.
func get_valid_values() -> Dictionary:
return {}
## Checks if the loaded values are valid for the element.
## If a value is wrong, it will be fixed automatically.
func verify_settings_data(VALUES_: Dictionary) -> bool:
# Check if an entry exists for the element
if not entry_exists():
return false
# Get the value of the element
var RETRIEVED_VALUE = SettingsDataManager.settingsData_[SECTION][IDENTIFIER]
# Check if the retrieved value is the correct type
if not is_valid_type(VALUES_, RETRIEVED_VALUE):
return false
# Check if the retrieved value has the expected value
if not is_valid_value(VALUES_, RETRIEVED_VALUE):
return false
return true
## Used by verify_settings_data() to check if the element
## and the section it is under exists in the settings data.
func entry_exists() -> bool:
# Check if the section exists in settings data
if not SettingsDataManager.settingsData_.has(SECTION):
push_warning("Settings section missing: ", SECTION)
return false
# Check if the element exists in the settings data
if not SettingsDataManager.settingsData_[SECTION].has(IDENTIFIER):
push_warning("Settings element is missing: ", IDENTIFIER)
return false
return true
## Used by verify_settings_data() to check if the retrieved value has the correct type.
func is_valid_type(VALUES_: Dictionary, RETRIEVED_VALUE) -> bool:
if typeof(RETRIEVED_VALUE) != typeof(VALUES_["defaultValue"]):
push_warning(
"Invalid value type of '"
+ type_string(typeof(RETRIEVED_VALUE))
+ "' for element '"
+ IDENTIFIER
+ "' expected value type of '"
+ type_string(typeof(VALUES_["defaultValue"]))
+ "'"
)
return false
return true
## Used by verify_settings_data() to check if the retrieved value has a valid value.
func is_valid_value(VALUES_: Dictionary, RETRIEVED_VALUE) -> bool:
# Get the type of the valid value
match typeof(VALUES_["defaultValue"]):
# If the type is either string or bool
TYPE_STRING, TYPE_BOOL:
# Check if the retrieved value is valid
if not VALUES_["validOptions"].has(RETRIEVED_VALUE):
push_warning(
"Invalid value '"
+ str(RETRIEVED_VALUE)
+ "' for element '"
+ IDENTIFIER
+ "' expected values: "
+ str(VALUES_["validOptions"])
)
return false
# If the type is either int or float
TYPE_INT, TYPE_FLOAT:
# Check if the retrieved value is valid
if (
RETRIEVED_VALUE < VALUES_["minValue"]
or RETRIEVED_VALUE > VALUES_["maxValue"]
):
# Special check if max fps is set to 0 (unlimited)
if IDENTIFIER == "MaxFPS" and RETRIEVED_VALUE == 0:
return true
push_warning(
"Invalid value "
+ str(RETRIEVED_VALUE)
+ " for element '"
+ IDENTIFIER
+ "' expected values between "
+ str(VALUES_["minValue"])
+ " and "
+ str(VALUES_["maxValue"])
)
return false
return true
## Used to apply in game settings that require a node to exist to be applied,
## i.e., world environment related settings and or most gameplay settings.
func apply_in_game_setting(value = null) -> bool:
if ParentRef.SettingsMenuRef.IS_IN_GAME_MENU:
SettingsDataManager.call_deferred(
"emit_signal",
"applied_in_game_setting",
SECTION,
IDENTIFIER,
value
)
return true
return false
## Called to apply the setting to the game.
## This function is overwritten by each [b]element[/b].
func _apply_settings() -> void:
return

View File

@@ -0,0 +1,96 @@
extends SettingsElement
class_name SliderElement
## A settings element specifically for elements that have a slider.
# Default values for the element
@export var MIN_VALUE: float = 0
@export var MAX_VALUE: float = 1
@export var STEP_VALUE: float = 0.1
@export var DEFAULT_VALUE: float = 1
## If true, displays 0 to 100 instead of 0 to 1 in the settings,
## but the true value remains the same.
@export var DISPLAY_PERCENT_VALUE: bool = false
## An extra suffix for the value (optional).
@export var VALUE_SUFFIX: String = ""
## Reference to the slider of the element.
@export var SliderRef: HSlider
## Reference to the SpinBox or Label of the element.
@export var ValueBoxRef: Control
## Overwrite for SettingsElement.
func init_element() -> void:
init_slider(100 if DISPLAY_PERCENT_VALUE else 1)
## Called to initialize the slider element.
func init_slider(FACTOR: float) -> void:
# Apply the min/max/step/current value of the SliderRef
SliderRef.set_min(MIN_VALUE * FACTOR)
SliderRef.set_max(MAX_VALUE * FACTOR)
SliderRef.set_step(STEP_VALUE * FACTOR)
SliderRef.set_value(currentValue * FACTOR)
# Connect the value changed signal for the SliderRef
if not SliderRef.is_connected("value_changed", slider_value_changed):
SliderRef.connect("value_changed", slider_value_changed.bind(FACTOR))
# Check if the value box is a spin box or a label
if ValueBoxRef is SpinBox:
# Apply the min/max/step/current value of the spin box
ValueBoxRef.set_min(MIN_VALUE * FACTOR)
ValueBoxRef.set_max(MAX_VALUE * FACTOR)
ValueBoxRef.set_step(STEP_VALUE * FACTOR)
ValueBoxRef.set_value(currentValue * FACTOR)
# Add caret blink to spin box
ValueBoxRef.get_line_edit().set_caret_blink_enabled(true)
ValueBoxRef.set_suffix(VALUE_SUFFIX)
# Connect the value changed signal of the spin box
if not ValueBoxRef.is_connected("value_changed", value_box_value_changed):
ValueBoxRef.connect("value_changed", value_box_value_changed)
else:
# Set the text as the current value
ValueBoxRef.set_text(str(currentValue) + VALUE_SUFFIX)
## Gets the valid values from the element to be used for validating data.
func get_valid_values() -> Dictionary:
# Check if value is out of bounds
if DEFAULT_VALUE > MAX_VALUE or DEFAULT_VALUE < MIN_VALUE:
push_warning("Invalid default value for element '" + IDENTIFIER + "'.")
DEFAULT_VALUE = clampf(DEFAULT_VALUE, MIN_VALUE, MAX_VALUE)
return {
"defaultValue": DEFAULT_VALUE,
"minValue": MIN_VALUE,
"maxValue": MAX_VALUE,
}
## Used to update values of the section cache the element is under.
func value_changed(value: float) -> void:
# Check if the settings menu is open
if ParentRef.settingsCache_.size() > 0:
ParentRef.settingsCache_[IDENTIFIER] = value
# Check if the new value is different than the saved value
ParentRef.settings_changed(IDENTIFIER)
currentValue = value
func slider_value_changed(value: float, FACTOR: float) -> void:
if ValueBoxRef is SpinBox:
ValueBoxRef.set_value(value)
else:
ValueBoxRef.set_text(str(value) + VALUE_SUFFIX)
value_changed(value / FACTOR)
func value_box_value_changed(value: float) -> void:
SliderRef.set_value(value)

View File

@@ -0,0 +1,38 @@
extends SettingsElement
class_name ToggleElement
## A settings element specifically for elements that have a toggle button.
## Default value for the element
@export var DEFAULT_VALUE: bool = false
## Reference to the toggle button of the element.
@export var ToggleRef: Button
func _ready() -> void:
super._ready()
ToggleRef.connect("toggled", toggled)
## Overwrite for SettingsElement.
func init_element() -> void:
ToggleRef.set_pressed(currentValue)
## Gets the valid values from the element to be used for validating data.
func get_valid_values() -> Dictionary:
return {
"defaultValue": DEFAULT_VALUE,
"validOptions": [true, false]
}
## Used to update values of the section cache the element is under.
func toggled(state: bool) -> void:
# Check if the settings menu is open
if ParentRef.settingsCache_.size() > 0:
# Update the settings cache with the new toggle state
ParentRef.settingsCache_[IDENTIFIER] = state
# Check if the new state is different than the saved state
ParentRef.settings_changed(IDENTIFIER)
currentValue = state