aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Spartan322 <Megacake1234@gmail.com>2023-02-10 10:18:46 +0100
committer Spartan322 <Megacake1234@gmail.com>2023-02-10 10:31:28 +0100
commit3798205c740e7e2faf2594866cb497260012508c (patch)
tree4ca4a0835cb833fbba1983f0e8de5fa66227b86e
parent6525b89a37a31eaf88182b11410bd46b6658e297 (diff)
Implement a usable settings UI, should fulfill:
SS-58, SS-61, SS-6, SS-9, SS-10, SS-11, SS-13 UI-11, UI-12, UI-19, UI-44, UI-47, UI-22
-rw-r--r--game/default_bus_layout.tres15
-rw-r--r--game/icon.svg.import2
-rw-r--r--game/project.godot24
-rw-r--r--game/src/Autoload/Resolution.gd43
-rw-r--r--game/src/GameMenu.gd13
-rw-r--r--game/src/GameMenu.tscn41
-rw-r--r--game/src/LocaleButton.gd26
-rw-r--r--game/src/LocaleButton.tscn12
-rw-r--r--game/src/MainMenu.gd12
-rw-r--r--game/src/MainMenu.tscn60
-rw-r--r--game/src/OptionMenu/MonitorDisplaySelector.gd16
-rw-r--r--game/src/OptionMenu/OptionsMenu.gd72
-rw-r--r--game/src/OptionMenu/OptionsMenu.tscn181
-rw-r--r--game/src/OptionMenu/ResolutionSelector.gd24
-rw-r--r--game/src/OptionMenu/ScreenModeSelector.gd33
-rw-r--r--game/src/OptionMenu/SettingNodes/SettingHSlider.gd20
-rw-r--r--game/src/OptionMenu/SettingNodes/SettingOptionButton.gd22
-rw-r--r--game/src/OptionMenu/VolumeGrid.gd54
-rw-r--r--game/src/OptionMenu/VolumeGrid.tscn8
-rw-r--r--game/src/OptionsMenu.gd43
-rw-r--r--game/src/OptionsMenu.tscn141
21 files changed, 641 insertions, 221 deletions
diff --git a/game/default_bus_layout.tres b/game/default_bus_layout.tres
new file mode 100644
index 0000000..87413af
--- /dev/null
+++ b/game/default_bus_layout.tres
@@ -0,0 +1,15 @@
+[gd_resource type="AudioBusLayout" format=3 uid="uid://cquqx51trot64"]
+
+[resource]
+bus/1/name = &"Music"
+bus/1/solo = false
+bus/1/mute = false
+bus/1/bypass_fx = false
+bus/1/volume_db = 0.0
+bus/1/send = &"Master"
+bus/2/name = &"SFX"
+bus/2/solo = false
+bus/2/mute = false
+bus/2/bypass_fx = false
+bus/2/volume_db = 0.0
+bus/2/send = &"Master"
diff --git a/game/icon.svg.import b/game/icon.svg.import
index b124f12..00642a8 100644
--- a/game/icon.svg.import
+++ b/game/icon.svg.import
@@ -16,9 +16,9 @@ dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.cte
[params]
compress/mode=0
+compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
-compress/bptc_ldr=0
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
diff --git a/game/project.godot b/game/project.godot
index 653cc91..d66f91f 100644
--- a/game/project.godot
+++ b/game/project.godot
@@ -11,6 +11,28 @@ config_version=5
[application]
config/name="OpenVic2"
-run/main_scene="res://src/MainMenu.tscn"
+run/main_scene="res://src/GameMenu.tscn"
config/features=PackedStringArray("4.0", "Forward Plus")
config/icon="res://icon.svg"
+
+[autoload]
+
+Resolution="*res://src/Autoload/Resolution.gd"
+
+[display]
+
+window/size/viewport_width=1280
+window/size/viewport_height=720
+window/size/mode=3
+window/size/resizable=false
+window/stretch/mode="canvas_items"
+window/stretch/aspect="ignore"
+
+[internationalization]
+
+locale/translation_remaps={}
+locale/fallback="en_GB"
+locale/locale_filter_mode=0
+locale/country_short_name={
+"United States of America": "USA"
+}
diff --git a/game/src/Autoload/Resolution.gd b/game/src/Autoload/Resolution.gd
new file mode 100644
index 0000000..cde46f5
--- /dev/null
+++ b/game/src/Autoload/Resolution.gd
@@ -0,0 +1,43 @@
+extends Node
+
+const _resolutions := {
+ &"3840x2160": Vector2i(3840,2160),
+ &"2560x1440": Vector2i(2560,1080),
+ &"1920x1080": Vector2i(1920,1080),
+ &"1366x768": Vector2i(1366,768),
+ &"1536x864": Vector2i(1536,864),
+ &"1280x720": Vector2i(1280,720),
+ &"1440x900": Vector2i(1440,900),
+ &"1600x900": Vector2i(1600,900),
+ &"1024x600": Vector2i(1024,600),
+ &"800x600": Vector2i(800,600)
+}
+
+func has_resolution(resolution_name : StringName) -> bool:
+ return resolution_name in _resolutions
+
+func get_resolution(resolution_name : StringName, default : Vector2i = Vector2i(1920, 1080)) -> Vector2i:
+ return _resolutions.get(resolution_name, default)
+
+func get_resolution_name_list() -> Array:
+ return _resolutions.keys()
+
+func get_current_resolution() -> Vector2:
+ var window := get_viewport().get_window()
+ match window.mode:
+ Window.MODE_EXCLUSIVE_FULLSCREEN, Window.MODE_FULLSCREEN:
+ return window.content_scale_size
+ _:
+ return window.size
+
+func set_resolution(resolution : Vector2) -> void:
+ var window := get_viewport().get_window()
+ match window.mode:
+ Window.MODE_EXCLUSIVE_FULLSCREEN, Window.MODE_FULLSCREEN:
+ window.content_scale_size = resolution
+ _:
+ window.size = resolution
+ window.content_scale_size = Vector2i(0,0)
+
+func reset_resolution() -> void:
+ set_resolution(get_current_resolution())
diff --git a/game/src/GameMenu.gd b/game/src/GameMenu.gd
new file mode 100644
index 0000000..1f5a77e
--- /dev/null
+++ b/game/src/GameMenu.gd
@@ -0,0 +1,13 @@
+extends Control
+
+
+func _on_main_menu_options_button_pressed():
+ $OptionsMenu.toggle_locale_button_visibility(false)
+ $OptionsMenu.show()
+ $MainMenu.hide()
+
+
+func _on_options_menu_back_button_pressed():
+ $MainMenu.show()
+ $OptionsMenu.hide()
+ $OptionsMenu.toggle_locale_button_visibility(true)
diff --git a/game/src/GameMenu.tscn b/game/src/GameMenu.tscn
new file mode 100644
index 0000000..24f1c6c
--- /dev/null
+++ b/game/src/GameMenu.tscn
@@ -0,0 +1,41 @@
+[gd_scene load_steps=5 format=3 uid="uid://b4pg2y2ivib8f"]
+
+[ext_resource type="Script" path="res://src/GameMenu.gd" id="1_cafwe"]
+[ext_resource type="PackedScene" uid="uid://dvoin538iby54" path="res://src/MainMenu.tscn" id="1_y4m2a"]
+[ext_resource type="PackedScene" uid="uid://cnbfxjy1m6wja" path="res://src/OptionMenu/OptionsMenu.tscn" id="3_111lv"]
+[ext_resource type="PackedScene" uid="uid://b7oncobnacxmt" path="res://src/LocaleButton.tscn" id="4_jno35"]
+
+[node name="GameMenu" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_cafwe")
+
+[node name="MainMenu" parent="." instance=ExtResource("1_y4m2a")]
+layout_mode = 1
+
+[node name="OptionsMenu" parent="." instance=ExtResource("3_111lv")]
+visible = false
+layout_mode = 1
+
+[node name="HBox" type="HBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -150.0
+offset_top = -20.0
+grow_horizontal = 0
+grow_vertical = 0
+alignment = 2
+
+[node name="LocaleButton" parent="HBox" instance=ExtResource("4_jno35")]
+layout_mode = 2
+
+[connection signal="options_button_pressed" from="MainMenu" to="." method="_on_main_menu_options_button_pressed"]
+[connection signal="back_button_pressed" from="OptionsMenu" to="." method="_on_options_menu_back_button_pressed"]
diff --git a/game/src/LocaleButton.gd b/game/src/LocaleButton.gd
new file mode 100644
index 0000000..eed815e
--- /dev/null
+++ b/game/src/LocaleButton.gd
@@ -0,0 +1,26 @@
+extends OptionButton
+
+var _locales_country_rename : Dictionary
+var _locales_list : Array[String]
+
+func _ready():
+ print("Loading locale button")
+
+ _locales_country_rename = ProjectSettings.get_setting("internationalization/locale/country_short_name", {})
+
+ _locales_list = [TranslationServer.get_locale()]
+ _locales_list.append_array(TranslationServer.get_loaded_locales())
+
+ for locale in _locales_list:
+ var locale_name := TranslationServer.get_locale_name(locale)
+ var locale_first_part := locale_name.get_slice(", ", 0)
+ var locale_second_part := locale_name.substr(locale_first_part.length() + 2)
+ if locale_second_part in _locales_country_rename:
+ locale_second_part = _locales_country_rename[locale_second_part]
+
+ add_item("%s, %s" % [locale_first_part, locale_second_part])
+
+
+func _on_item_selected(index):
+ print("Selected locale " + _locales_list[index])
+ TranslationServer.set_locale(_locales_list[index])
diff --git a/game/src/LocaleButton.tscn b/game/src/LocaleButton.tscn
new file mode 100644
index 0000000..55f1c29
--- /dev/null
+++ b/game/src/LocaleButton.tscn
@@ -0,0 +1,12 @@
+[gd_scene load_steps=2 format=3 uid="uid://b7oncobnacxmt"]
+
+[ext_resource type="Script" path="res://src/LocaleButton.gd" id="1_ganev"]
+
+[node name="LocaleButton" type="OptionButton"]
+custom_minimum_size = Vector2(150, 0)
+alignment = 2
+text_overrun_behavior = 2
+fit_to_longest_item = false
+script = ExtResource("1_ganev")
+
+[connection signal="item_selected" from="." to="." method="_on_item_selected"]
diff --git a/game/src/MainMenu.gd b/game/src/MainMenu.gd
index a9cdf10..a5c6198 100644
--- a/game/src/MainMenu.gd
+++ b/game/src/MainMenu.gd
@@ -1,17 +1,11 @@
extends Control
+signal options_button_pressed
-# Called when the node enters the scene tree for the first time.
func _ready():
print("From GDScript")
TestSingleton.hello_singleton()
- $CenterContainer/VBoxContainer/NewGameButton.grab_focus()
- pass # Replace with function body.
-
-
-## Called every frame. 'delta' is the elapsed time since the previous frame.
-#func _process(delta):
-# pass
+ $VBox/Center/VBox/NewGameButton.grab_focus()
func _on_new_game_button_pressed():
@@ -28,7 +22,7 @@ func _on_multi_player_button_pressed():
func _on_options_button_pressed():
print("Check out some options!")
- get_tree().change_scene_to_file("res://src/OptionsMenu.tscn")
+ options_button_pressed.emit()
func _on_exit_button_pressed():
diff --git a/game/src/MainMenu.tscn b/game/src/MainMenu.tscn
index 5a2cb8a..587eb01 100644
--- a/game/src/MainMenu.tscn
+++ b/game/src/MainMenu.tscn
@@ -1,58 +1,66 @@
-[gd_scene load_steps=2 format=3 uid="uid://b4pg2y2ivib8f"]
+[gd_scene load_steps=2 format=3 uid="uid://dvoin538iby54"]
[ext_resource type="Script" path="res://src/MainMenu.gd" id="1_6akq8"]
[node name="MainMenu" type="Control"]
layout_mode = 3
-anchors_preset = 0
-offset_right = 1152.0
-offset_bottom = 648.0
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
script = ExtResource("1_6akq8")
-[node name="Label" type="Label" parent="."]
-layout_mode = 0
-offset_top = 137.0
-offset_right = 1152.0
-offset_bottom = 264.0
+[node name="VBox" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="TitleLabel" type="Label" parent="VBox"]
+layout_mode = 2
+size_flags_vertical = 6
+size_flags_stretch_ratio = 1.5
theme_override_font_sizes/font_size = 90
text = "OpenVic2"
horizontal_alignment = 1
+vertical_alignment = 1
-[node name="CenterContainer" type="CenterContainer" parent="."]
-layout_mode = 0
-offset_top = 268.0
-offset_right = 1152.0
-offset_bottom = 648.0
+[node name="Center" type="CenterContainer" parent="VBox"]
+layout_mode = 2
+size_flags_vertical = 2
-[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
+[node name="VBox" type="VBoxContainer" parent="VBox/Center"]
layout_mode = 2
-[node name="NewGameButton" type="Button" parent="CenterContainer/VBoxContainer" node_paths=PackedStringArray("shortcut_context")]
+[node name="NewGameButton" type="Button" parent="VBox/Center/VBox" node_paths=PackedStringArray("shortcut_context")]
layout_mode = 2
focus_neighbor_top = NodePath("../ExitButton")
shortcut_context = NodePath("")
text = "New Game"
-[node name="ContinueButton" type="Button" parent="CenterContainer/VBoxContainer"]
+[node name="ContinueButton" type="Button" parent="VBox/Center/VBox"]
layout_mode = 2
disabled = true
text = "Continue"
-[node name="MultiPlayerButton" type="Button" parent="CenterContainer/VBoxContainer"]
+[node name="MultiplayerButton" type="Button" parent="VBox/Center/VBox"]
layout_mode = 2
-text = "Multi-Player"
+text = "Multipayer"
-[node name="OptionsButton" type="Button" parent="CenterContainer/VBoxContainer"]
+[node name="OptionsButton" type="Button" parent="VBox/Center/VBox"]
layout_mode = 2
text = "Options"
-[node name="ExitButton" type="Button" parent="CenterContainer/VBoxContainer"]
+[node name="ExitButton" type="Button" parent="VBox/Center/VBox"]
layout_mode = 2
focus_neighbor_bottom = NodePath("../NewGameButton")
text = "Exit"
-[connection signal="pressed" from="CenterContainer/VBoxContainer/NewGameButton" to="." method="_on_new_game_button_pressed"]
-[connection signal="pressed" from="CenterContainer/VBoxContainer/ContinueButton" to="." method="_on_continue_button_pressed"]
-[connection signal="pressed" from="CenterContainer/VBoxContainer/MultiPlayerButton" to="." method="_on_multi_player_button_pressed"]
-[connection signal="pressed" from="CenterContainer/VBoxContainer/OptionsButton" to="." method="_on_options_button_pressed"]
-[connection signal="pressed" from="CenterContainer/VBoxContainer/ExitButton" to="." method="_on_exit_button_pressed"]
+[connection signal="pressed" from="VBox/Center/VBox/NewGameButton" to="." method="_on_new_game_button_pressed"]
+[connection signal="pressed" from="VBox/Center/VBox/ContinueButton" to="." method="_on_continue_button_pressed"]
+[connection signal="pressed" from="VBox/Center/VBox/MultiplayerButton" to="." method="_on_multi_player_button_pressed"]
+[connection signal="pressed" from="VBox/Center/VBox/OptionsButton" to="." method="_on_options_button_pressed"]
+[connection signal="pressed" from="VBox/Center/VBox/ExitButton" to="." method="_on_exit_button_pressed"]
diff --git a/game/src/OptionMenu/MonitorDisplaySelector.gd b/game/src/OptionMenu/MonitorDisplaySelector.gd
new file mode 100644
index 0000000..600b296
--- /dev/null
+++ b/game/src/OptionMenu/MonitorDisplaySelector.gd
@@ -0,0 +1,16 @@
+extends SettingOptionButton
+
+
+func _ready():
+ clear()
+ for screen_index in range(DisplayServer.get_screen_count()):
+ add_item("Monitor %d" % (screen_index + 1))
+
+func _on_item_selected(index):
+ print("Selected index: %d" % index)
+ var window := get_viewport().get_window()
+ var mode := window.mode
+ window.mode = Window.MODE_WINDOWED
+ get_viewport().get_window().set_current_screen(index)
+ window.mode = mode
+ print(get_viewport().get_window().current_screen)
diff --git a/game/src/OptionMenu/OptionsMenu.gd b/game/src/OptionMenu/OptionsMenu.gd
new file mode 100644
index 0000000..e3c8433
--- /dev/null
+++ b/game/src/OptionMenu/OptionsMenu.gd
@@ -0,0 +1,72 @@
+extends Control
+
+@export
+var user_settings_file_path : String = "settings.cfg"
+
+signal back_button_pressed
+
+signal save_settings(save_file: ConfigFile)
+signal load_settings(load_file: ConfigFile)
+signal reset_settings()
+
+@onready
+var _settings_file_path := "user://" + user_settings_file_path
+var _settings_file := ConfigFile.new()
+
+func _ready():
+ # Prepare options menu before loading user settings
+
+ print("TODO: Load user settings!")
+
+ if FileAccess.file_exists(_settings_file_path):
+ _settings_file.load(_settings_file_path)
+ load_settings.emit(_settings_file)
+
+ var tab_bar : TabBar = $Margin/Tab.get_child(0, true)
+
+ # This ends up easier to manage then trying to manually recreate the TabContainer's behavior
+ # These buttons can be accessed regardless of the tab
+ var button_list := HBoxContainer.new()
+ button_list.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
+ button_list.alignment = BoxContainer.ALIGNMENT_END
+ tab_bar.add_child(button_list)
+
+ var reset_button := Button.new()
+ reset_button.text = "R"
+ reset_button.pressed.connect(func(): reset_settings.emit())
+ button_list.add_child(reset_button)
+
+ var back_button := Button.new()
+ back_button.text = "X"
+ back_button.pressed.connect(_on_back_button_pressed)
+ button_list.add_child(back_button)
+
+ get_viewport().get_window().close_requested.connect(_on_window_close_requested)
+
+func _notification(what):
+ match what:
+ NOTIFICATION_CRASH:
+ _on_window_close_requested()
+
+# Could pass the LocaleButton between the MainMenu and OptionsMenu
+# but that seems a bit excessive
+func toggle_locale_button_visibility(locale_visible : bool):
+ print("Toggling locale button: %s" % locale_visible)
+ $LocaleVBox/LocaleHBox/LocaleButton.visible = locale_visible
+
+func _on_ear_exploder_toggled(button_pressed):
+ print("KABOOM!!!" if button_pressed else "DEFUSED!!!")
+
+
+func _on_back_button_pressed():
+ save_settings.emit(_settings_file)
+ _settings_file.save(_settings_file_path)
+ back_button_pressed.emit()
+
+
+func _on_spin_box_value_changed(value):
+ print("Spinbox: %d" % value)
+
+func _on_window_close_requested() -> void:
+ if visible:
+ _on_back_button_pressed()
diff --git a/game/src/OptionMenu/OptionsMenu.tscn b/game/src/OptionMenu/OptionsMenu.tscn
new file mode 100644
index 0000000..0ed531f
--- /dev/null
+++ b/game/src/OptionMenu/OptionsMenu.tscn
@@ -0,0 +1,181 @@
+[gd_scene load_steps=7 format=3 uid="uid://cnbfxjy1m6wja"]
+
+[ext_resource type="Script" path="res://src/OptionMenu/OptionsMenu.gd" id="1_tlein"]
+[ext_resource type="PackedScene" uid="uid://b7oncobnacxmt" path="res://src/LocaleButton.tscn" id="2_d7wvq"]
+[ext_resource type="Script" path="res://src/OptionMenu/ResolutionSelector.gd" id="2_jk1ey"]
+[ext_resource type="Script" path="res://src/OptionMenu/ScreenModeSelector.gd" id="3_hsicf"]
+[ext_resource type="Script" path="res://src/OptionMenu/MonitorDisplaySelector.gd" id="3_q1cm3"]
+[ext_resource type="PackedScene" uid="uid://dy4si8comamnv" path="res://src/OptionMenu/VolumeGrid.tscn" id="4_n4oqr"]
+
+[node name="OptionsMenu" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_tlein")
+
+[node name="Margin" type="MarginContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/margin_left = 250
+theme_override_constants/margin_top = 100
+theme_override_constants/margin_right = 250
+theme_override_constants/margin_bottom = 200
+
+[node name="Tab" type="TabContainer" parent="Margin"]
+layout_mode = 2
+size_flags_vertical = 3
+tab_alignment = 1
+use_hidden_tabs_for_min_size = true
+
+[node name="Video" type="HBoxContainer" parent="Margin/Tab"]
+layout_mode = 2
+tooltip_text = "This is my cool and very nice tooltip"
+alignment = 1
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Margin/Tab/Video"]
+layout_mode = 2
+
+[node name="Control" type="Control" parent="Margin/Tab/Video/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.1
+
+[node name="GridContainer" type="GridContainer" parent="Margin/Tab/Video/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+columns = 2
+
+[node name="ResolutionLabel" type="Label" parent="Margin/Tab/Video/VBoxContainer/GridContainer"]
+layout_mode = 2
+text = "Resolution"
+
+[node name="ResolutionSelector" type="OptionButton" parent="Margin/Tab/Video/VBoxContainer/GridContainer"]
+layout_mode = 2
+item_count = 1
+selected = 0
+popup/item_0/text = "MISSING"
+popup/item_0/id = 0
+script = ExtResource("2_jk1ey")
+section_name = "Video"
+setting_name = "Resolution"
+default_value = -1
+
+[node name="ScreenModeLabel" type="Label" parent="Margin/Tab/Video/VBoxContainer/GridContainer"]
+layout_mode = 2
+text = "Screen Mode"
+
+[node name="ScreenModeSelector" type="OptionButton" parent="Margin/Tab/Video/VBoxContainer/GridContainer"]
+layout_mode = 2
+item_count = 3
+selected = 0
+popup/item_0/text = "Fullscreen"
+popup/item_0/id = 0
+popup/item_1/text = "Borderless"
+popup/item_1/id = 1
+popup/item_2/text = "Windowed"
+popup/item_2/id = 2
+script = ExtResource("3_hsicf")
+section_name = "Video"
+setting_name = "Mode Selected"
+
+[node name="MonitorSelectionLabel" type="Label" parent="Margin/Tab/Video/VBoxContainer/GridContainer"]
+layout_mode = 2
+text = "Monitor Selection"
+
+[node name="MonitorDisplaySelector" type="OptionButton" parent="Margin/Tab/Video/VBoxContainer/GridContainer"]
+layout_mode = 2
+item_count = 1
+selected = 0
+popup/item_0/text = "MISSING"
+popup/item_0/id = 0
+script = ExtResource("3_q1cm3")
+section_name = "Video"
+setting_name = "Current Screen"
+
+[node name="Sound" type="HBoxContainer" parent="Margin/Tab"]
+visible = false
+layout_mode = 2
+alignment = 1
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Margin/Tab/Sound"]
+layout_mode = 2
+
+[node name="Control" type="Control" parent="Margin/Tab/Sound/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+size_flags_stretch_ratio = 0.1
+
+[node name="VolumeGrid" parent="Margin/Tab/Sound/VBoxContainer" instance=ExtResource("4_n4oqr")]
+layout_mode = 2
+
+[node name="ButtonGrid" type="GridContainer" parent="Margin/Tab/Sound/VBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 2
+columns = 2
+
+[node name="Spacer" type="Control" parent="Margin/Tab/Sound/VBoxContainer/ButtonGrid"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="EarExploder" type="CheckButton" parent="Margin/Tab/Sound/VBoxContainer/ButtonGrid"]
+layout_mode = 2
+text = "Explode Eardrums on Startup?"
+
+[node name="Other" type="Control" parent="Margin/Tab"]
+visible = false
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="Margin/Tab/Other"]
+layout_mode = 0
+offset_right = 40.0
+offset_bottom = 40.0
+
+[node name="Label" type="Label" parent="Margin/Tab/Other/HBoxContainer"]
+layout_mode = 2
+text = "Spinbox Example :)"
+
+[node name="SpinBox" type="SpinBox" parent="Margin/Tab/Other/HBoxContainer"]
+layout_mode = 2
+
+[node name="LocaleVBox" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+alignment = 2
+
+[node name="LocaleHBox" type="HBoxContainer" parent="LocaleVBox"]
+layout_mode = 2
+mouse_filter = 2
+alignment = 2
+
+[node name="LocaleButton" parent="LocaleVBox/LocaleHBox" instance=ExtResource("2_d7wvq")]
+layout_mode = 2
+
+[connection signal="load_settings" from="." to="Margin/Tab/Video/VBoxContainer/GridContainer/ResolutionSelector" method="load_setting"]
+[connection signal="load_settings" from="." to="Margin/Tab/Video/VBoxContainer/GridContainer/ScreenModeSelector" method="load_setting"]
+[connection signal="load_settings" from="." to="Margin/Tab/Video/VBoxContainer/GridContainer/MonitorDisplaySelector" method="load_setting"]
+[connection signal="load_settings" from="." to="Margin/Tab/Sound/VBoxContainer/VolumeGrid" method="_on_options_menu_load_settings"]
+[connection signal="reset_settings" from="." to="Margin/Tab/Video/VBoxContainer/GridContainer/ResolutionSelector" method="reset_setting"]
+[connection signal="reset_settings" from="." to="Margin/Tab/Video/VBoxContainer/GridContainer/ScreenModeSelector" method="reset_setting"]
+[connection signal="reset_settings" from="." to="Margin/Tab/Video/VBoxContainer/GridContainer/MonitorDisplaySelector" method="reset_setting"]
+[connection signal="reset_settings" from="." to="Margin/Tab/Sound/VBoxContainer/VolumeGrid" method="_on_options_menu_reset_settings"]
+[connection signal="save_settings" from="." to="Margin/Tab/Video/VBoxContainer/GridContainer/ResolutionSelector" method="save_setting"]
+[connection signal="save_settings" from="." to="Margin/Tab/Video/VBoxContainer/GridContainer/ScreenModeSelector" method="save_setting"]
+[connection signal="save_settings" from="." to="Margin/Tab/Video/VBoxContainer/GridContainer/MonitorDisplaySelector" method="save_setting"]
+[connection signal="save_settings" from="." to="Margin/Tab/Sound/VBoxContainer/VolumeGrid" method="_on_options_menu_save_settings"]
+[connection signal="item_selected" from="Margin/Tab/Video/VBoxContainer/GridContainer/ResolutionSelector" to="Margin/Tab/Video/VBoxContainer/GridContainer/ResolutionSelector" method="_on_item_selected"]
+[connection signal="item_selected" from="Margin/Tab/Video/VBoxContainer/GridContainer/ScreenModeSelector" to="Margin/Tab/Video/VBoxContainer/GridContainer/ScreenModeSelector" method="_on_item_selected"]
+[connection signal="item_selected" from="Margin/Tab/Video/VBoxContainer/GridContainer/MonitorDisplaySelector" to="Margin/Tab/Video/VBoxContainer/GridContainer/MonitorDisplaySelector" method="_on_item_selected"]
+[connection signal="toggled" from="Margin/Tab/Sound/VBoxContainer/ButtonGrid/EarExploder" to="." method="_on_ear_exploder_toggled"]
+[connection signal="value_changed" from="Margin/Tab/Other/HBoxContainer/SpinBox" to="." method="_on_spin_box_value_changed"]
diff --git a/game/src/OptionMenu/ResolutionSelector.gd b/game/src/OptionMenu/ResolutionSelector.gd
new file mode 100644
index 0000000..ef1a0ff
--- /dev/null
+++ b/game/src/OptionMenu/ResolutionSelector.gd
@@ -0,0 +1,24 @@
+extends SettingOptionButton
+
+func _ready():
+ print("Resolution selector ready")
+
+ clear()
+ var resolution_index := 0
+ for resolution in Resolution.get_resolution_name_list():
+ add_item(resolution)
+
+ if Vector2(Resolution.get_resolution(resolution)) == Resolution.get_current_resolution():
+ if default_value == -1:
+ default_value = resolution_index
+ _select_int(resolution_index)
+ print(resolution)
+
+ resolution_index += 1
+
+
+func _on_item_selected(index):
+ print("Selected index: %d" % index)
+
+ var resolution_size : Vector2i = Resolution.get_resolution(get_item_text(index))
+ Resolution.set_resolution(resolution_size)
diff --git a/game/src/OptionMenu/ScreenModeSelector.gd b/game/src/OptionMenu/ScreenModeSelector.gd
new file mode 100644
index 0000000..fae0771
--- /dev/null
+++ b/game/src/OptionMenu/ScreenModeSelector.gd
@@ -0,0 +1,33 @@
+extends SettingOptionButton
+
+enum ScreenMode { Unknown = -1, Fullscreen, Borderless, Windowed }
+
+func get_screen_mode_from_window_mode(window_mode : int) -> ScreenMode:
+ match window_mode:
+ Window.MODE_EXCLUSIVE_FULLSCREEN:
+ return ScreenMode.Fullscreen
+ Window.MODE_FULLSCREEN:
+ return ScreenMode.Borderless
+ Window.MODE_WINDOWED:
+ return ScreenMode.Windowed
+ _:
+ return ScreenMode.Unknown
+
+func get_window_mode_from_screen_mode(screen_mode : int) -> Window.Mode:
+ match screen_mode:
+ ScreenMode.Fullscreen:
+ return Window.MODE_EXCLUSIVE_FULLSCREEN
+ ScreenMode.Borderless:
+ return Window.MODE_FULLSCREEN
+ ScreenMode.Windowed:
+ return Window.MODE_WINDOWED
+ _:
+ return Window.MODE_EXCLUSIVE_FULLSCREEN
+
+func _on_item_selected(index : int):
+ print("Selected index: %d" % index)
+
+ var window := get_viewport().get_window()
+ var current_resolution := Resolution.get_current_resolution()
+ window.mode = get_window_mode_from_screen_mode(index)
+ Resolution.set_resolution(current_resolution)
diff --git a/game/src/OptionMenu/SettingNodes/SettingHSlider.gd b/game/src/OptionMenu/SettingNodes/SettingHSlider.gd
new file mode 100644
index 0000000..da9348f
--- /dev/null
+++ b/game/src/OptionMenu/SettingNodes/SettingHSlider.gd
@@ -0,0 +1,20 @@
+extends HSlider
+class_name SettingHSlider
+
+@export
+var section_name : String = "Setting"
+
+@export
+var setting_name : String = "SettingHSlider"
+
+@export
+var default_value : float = 0
+
+func load_setting(file : ConfigFile):
+ value = file.get_value(section_name, setting_name, default_value)
+
+func save_setting(file : ConfigFile):
+ file.set_value(section_name, setting_name, value)
+
+func reset_setting():
+ value = default_value
diff --git a/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd b/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd
new file mode 100644
index 0000000..46fc825
--- /dev/null
+++ b/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd
@@ -0,0 +1,22 @@
+extends OptionButton
+class_name SettingOptionButton
+
+@export
+var section_name : String = "Setting"
+
+@export
+var setting_name : String = "SettingOptionMenu"
+
+@export
+var default_value : int = 0
+
+func load_setting(file : ConfigFile):
+ selected = file.get_value(section_name, setting_name, default_value)
+ item_selected.emit(selected)
+
+func save_setting(file : ConfigFile):
+ file.set_value(section_name, setting_name, selected)
+
+func reset_setting():
+ selected = default_value
+ item_selected.emit(selected)
diff --git a/game/src/OptionMenu/VolumeGrid.gd b/game/src/OptionMenu/VolumeGrid.gd
new file mode 100644
index 0000000..fae5ff6
--- /dev/null
+++ b/game/src/OptionMenu/VolumeGrid.gd
@@ -0,0 +1,54 @@
+extends GridContainer
+
+const RATIO_FOR_LINEAR = 100
+
+var _slider_dictionary := {}
+
+func get_db_as_volume_value(db : float) -> float:
+ # db_to_linear produces a float between 0 and 1 from a db value
+ return db_to_linear(db) * RATIO_FOR_LINEAR
+
+func get_volume_value_as_db(value : float) -> float:
+ # linear_to_db consumes a float between 0 and 1 to produce the db value
+ return linear_to_db(value / RATIO_FOR_LINEAR)
+
+func add_volume_column(bus_name : StringName, bus_index : int) -> HSlider:
+ var volume_label := Label.new()
+ volume_label.text = bus_name + " Volume"
+ add_child(volume_label)
+
+ var volume_slider := SettingHSlider.new()
+ volume_slider.section_name = "Audio"
+ volume_slider.setting_name = volume_label.text
+ volume_slider.custom_minimum_size = Vector2(290, 0)
+ volume_slider.size_flags_vertical = Control.SIZE_FILL
+ volume_slider.min_value = 0
+ volume_slider.default_value = 100
+ volume_slider.max_value = 120 # 120 so volume can be boosted somewhat
+ volume_slider.value_changed.connect(_on_slider_value_changed.bind(bus_index))
+ add_child(volume_slider)
+
+ _slider_dictionary[volume_label.text] = volume_slider
+ return volume_slider
+
+func _ready():
+ for bus_index in AudioServer.bus_count:
+ add_volume_column(AudioServer.get_bus_name(bus_index), bus_index)
+
+func _on_slider_value_changed(value : float, bus_index : int) -> void:
+ AudioServer.set_bus_volume_db(bus_index, get_volume_value_as_db(value))
+
+
+func _on_options_menu_load_settings(load_file : ConfigFile):
+ for volume_label_text in _slider_dictionary:
+ _slider_dictionary[volume_label_text].load_setting(load_file)
+
+
+func _on_options_menu_save_settings(save_file : ConfigFile):
+ for volume_label_text in _slider_dictionary:
+ _slider_dictionary[volume_label_text].save_setting(save_file)
+
+
+func _on_options_menu_reset_settings():
+ for volume_label_text in _slider_dictionary:
+ _slider_dictionary[volume_label_text].reset_setting()
diff --git a/game/src/OptionMenu/VolumeGrid.tscn b/game/src/OptionMenu/VolumeGrid.tscn
new file mode 100644
index 0000000..6d4de3c
--- /dev/null
+++ b/game/src/OptionMenu/VolumeGrid.tscn
@@ -0,0 +1,8 @@
+[gd_scene load_steps=2 format=3 uid="uid://dy4si8comamnv"]
+
+[ext_resource type="Script" path="res://src/OptionMenu/VolumeGrid.gd" id="1_wb64h"]
+
+[node name="VolumeGrid" type="GridContainer"]
+size_flags_vertical = 0
+columns = 2
+script = ExtResource("1_wb64h")
diff --git a/game/src/OptionsMenu.gd b/game/src/OptionsMenu.gd
deleted file mode 100644
index adcd3b5..0000000
--- a/game/src/OptionsMenu.gd
+++ /dev/null
@@ -1,43 +0,0 @@
-extends Control
-
-
-# Called when the node enters the scene tree for the first time.
-func _ready():
- print("TODO: Load user settings!")
- pass # Replace with function body.
-
-## Called every frame. 'delta' is the elapsed time since the previous frame.
-#func _process(delta):
-# pass
-
-func _on_resolution_selector_item_selected(index):
- print("Selected index: %d" % index)
-
-func _on_screen_mode_selector_item_selected(index):
- print("Selected index: %d" % index)
-
-func _on_monitor_display_selector_item_selected(index):
- print("Selected index: %d" % index)
-
-func _on_music_volume_value_changed(value):
- print("Music: %f" % value)
-
-
-func _on_sfx_volume_value_changed(value):
- print("SFX: %f" % value)
-
-
-func _on_ear_exploder_toggled(button_pressed):
- print("KABOOM!!!" if button_pressed else "DEFUSED!!!")
-
-
-func _on_save_settings_button_pressed():
- print("TODO: save current settings!")
-
-
-func _on_back_button_pressed():
- get_tree().change_scene_to_file("res://src/MainMenu.tscn")
-
-
-func _on_spin_box_value_changed(value):
- print("Spinbox: %d" % value)
diff --git a/game/src/OptionsMenu.tscn b/game/src/OptionsMenu.tscn
deleted file mode 100644
index 495d72c..0000000
--- a/game/src/OptionsMenu.tscn
+++ /dev/null
@@ -1,141 +0,0 @@
-[gd_scene load_steps=2 format=3 uid="uid://ch03lp7d7fvw3"]
-
-[ext_resource type="Script" path="res://src/OptionsMenu.gd" id="1_2tajv"]
-
-[node name="OptionsMenu" type="Control"]
-layout_mode = 3
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-script = ExtResource("1_2tajv")
-
-[node name="TabContainer" type="TabContainer" parent="."]
-layout_mode = 0
-offset_right = 1152.0
-offset_bottom = 648.0
-
-[node name="VideoTab" type="TabBar" parent="TabContainer"]
-layout_mode = 2
-tooltip_text = "This is my cool and very nice tooltip"
-
-[node name="GridContainer" type="GridContainer" parent="TabContainer/VideoTab"]
-layout_mode = 0
-offset_right = 40.0
-offset_bottom = 40.0
-columns = 2
-
-[node name="Label2" type="Label" parent="TabContainer/VideoTab/GridContainer"]
-layout_mode = 2
-text = "Resolution:"
-
-[node name="ResolutionSelector" type="OptionButton" parent="TabContainer/VideoTab/GridContainer"]
-layout_mode = 2
-item_count = 3
-selected = 0
-popup/item_0/text = "1920x1080"
-popup/item_0/id = 0
-popup/item_1/text = "1366x768"
-popup/item_1/id = 1
-popup/item_2/text = "1280x1024"
-popup/item_2/id = 2
-
-[node name="Label3" type="Label" parent="TabContainer/VideoTab/GridContainer"]
-layout_mode = 2
-text = "Screen Mode:"
-
-[node name="ScreenModeSelector" type="OptionButton" parent="TabContainer/VideoTab/GridContainer"]
-layout_mode = 2
-item_count = 3
-selected = 0
-popup/item_0/text = "Borderless"
-popup/item_0/id = 0
-popup/item_1/text = "Windowed"
-popup/item_1/id = 1
-popup/item_2/text = "Fullscreen"
-popup/item_2/id = 2
-
-[node name="Label" type="Label" parent="TabContainer/VideoTab/GridContainer"]
-layout_mode = 2
-text = "Monitor Selection:"
-
-[node name="MonitorDisplaySelector" type="OptionButton" parent="TabContainer/VideoTab/GridContainer"]
-layout_mode = 2
-item_count = 1
-selected = 0
-popup/item_0/text = "Monitor 1"
-popup/item_0/id = 0
-
-[node name="BackButton" type="Button" parent="TabContainer/VideoTab/GridContainer"]
-layout_mode = 2
-text = "Back to Main Menu"
-
-[node name="SaveSettingsButton" type="Button" parent="TabContainer/VideoTab/GridContainer"]
-layout_mode = 2
-text = "Save Me!"
-
-[node name="SoundTab" type="TabBar" parent="TabContainer"]
-visible = false
-layout_mode = 2
-
-[node name="GridContainer" type="GridContainer" parent="TabContainer/SoundTab"]
-layout_mode = 0
-offset_top = 33.0
-offset_right = 1152.0
-offset_bottom = 648.0
-columns = 2
-
-[node name="Label" type="Label" parent="TabContainer/SoundTab/GridContainer"]
-layout_mode = 2
-text = "Music Volume"
-horizontal_alignment = 2
-
-[node name="MusicVolume" type="HSlider" parent="TabContainer/SoundTab/GridContainer"]
-layout_mode = 2
-value = 100.0
-
-[node name="Label3" type="Label" parent="TabContainer/SoundTab/GridContainer"]
-layout_mode = 2
-text = "SFX Volume"
-horizontal_alignment = 2
-
-[node name="SfxVolume" type="HSlider" parent="TabContainer/SoundTab/GridContainer"]
-layout_mode = 2
-value = 100.0
-
-[node name="Label2" type="Label" parent="TabContainer/SoundTab/GridContainer"]
-layout_mode = 2
-horizontal_alignment = 2
-
-[node name="EarExploder" type="CheckButton" parent="TabContainer/SoundTab/GridContainer"]
-layout_mode = 2
-text = "Explode Eardrums on Startup?"
-
-[node name="OtherTab" type="TabBar" parent="TabContainer"]
-visible = false
-layout_mode = 2
-
-[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/OtherTab"]
-layout_mode = 0
-offset_right = 40.0
-offset_bottom = 40.0
-
-[node name="Label" type="Label" parent="TabContainer/OtherTab/HBoxContainer"]
-layout_mode = 2
-text = "Spinbox Example :)"
-
-[node name="SpinBox" type="SpinBox" parent="TabContainer/OtherTab/HBoxContainer"]
-layout_mode = 2
-
-[connection signal="item_selected" from="TabContainer/VideoTab/GridContainer/ResolutionSelector" to="." method="_on_resolution_selector_item_selected"]
-[connection signal="item_selected" from="TabContainer/VideoTab/GridContainer/ScreenModeSelector" to="." method="_on_screen_mode_selector_item_selected"]
-[connection signal="item_selected" from="TabContainer/VideoTab/GridContainer/MonitorDisplaySelector" to="." method="_on_monitor_display_selector_item_selected"]
-[connection signal="pressed" from="TabContainer/VideoTab/GridContainer/BackButton" to="." method="_on_back_button_pressed"]
-[connection signal="pressed" from="TabContainer/VideoTab/GridContainer/SaveSettingsButton" to="." method="_on_save_settings_button_pressed"]
-[connection signal="changed" from="TabContainer/SoundTab/GridContainer/MusicVolume" to="." method="_on_music_volume_changed"]
-[connection signal="drag_ended" from="TabContainer/SoundTab/GridContainer/MusicVolume" to="." method="_on_music_volume_drag_ended"]
-[connection signal="value_changed" from="TabContainer/SoundTab/GridContainer/MusicVolume" to="." method="_on_music_volume_value_changed"]
-[connection signal="value_changed" from="TabContainer/SoundTab/GridContainer/SfxVolume" to="." method="_on_sfx_volume_value_changed"]
-[connection signal="toggled" from="TabContainer/SoundTab/GridContainer/EarExploder" to="." method="_on_ear_exploder_toggled"]
-[connection signal="value_changed" from="TabContainer/OtherTab/HBoxContainer/SpinBox" to="." method="_on_spin_box_value_changed"]