diff options
Diffstat (limited to 'game/src/Game/Menu/OptionMenu')
23 files changed, 960 insertions, 0 deletions
diff --git a/game/src/Game/Menu/OptionMenu/AutosaveIntervalSelector.gd b/game/src/Game/Menu/OptionMenu/AutosaveIntervalSelector.gd new file mode 100644 index 0000000..2c55862 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/AutosaveIntervalSelector.gd @@ -0,0 +1,2 @@ +extends SettingOptionButton + diff --git a/game/src/Game/Menu/OptionMenu/ControlsTab.tscn b/game/src/Game/Menu/OptionMenu/ControlsTab.tscn new file mode 100644 index 0000000..b84dc85 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/ControlsTab.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=2 format=3 uid="uid://cdwymd51i4b2f"] + +[ext_resource type="PackedScene" uid="uid://by4gggse2nsdx" path="res://addons/keychain/ShortcutEdit.tscn" id="1_fv8sh"] + +[node name="Controls" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="ShortcutEdit" parent="." instance=ExtResource("1_fv8sh")] +layout_mode = 1 diff --git a/game/src/Game/Menu/OptionMenu/GeneralTab.gd b/game/src/Game/Menu/OptionMenu/GeneralTab.gd new file mode 100644 index 0000000..3d98678 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/GeneralTab.gd @@ -0,0 +1,9 @@ +extends HBoxContainer + +@export var initial_focus: Control + +func _notification(what : int) -> void: + match(what): + NOTIFICATION_VISIBILITY_CHANGED: + if visible and is_inside_tree(): + initial_focus.grab_focus() diff --git a/game/src/Game/Menu/OptionMenu/GeneralTab.tscn b/game/src/Game/Menu/OptionMenu/GeneralTab.tscn new file mode 100644 index 0000000..4e9ff6a --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/GeneralTab.tscn @@ -0,0 +1,81 @@ +[gd_scene load_steps=5 format=3 uid="uid://duwjal7sd7p6w"] + +[ext_resource type="Script" path="res://src/OptionMenu/GeneralTab.gd" id="1_gbutn"] +[ext_resource type="PackedScene" uid="uid://b7oncobnacxmt" path="res://src/LocaleButton.tscn" id="2_5cfd7"] +[ext_resource type="Script" path="res://src/OptionMenu/SettingNodes/SettingOptionButton.gd" id="2_msx2u"] +[ext_resource type="Script" path="res://src/OptionMenu/AutosaveIntervalSelector.gd" id="2_t06tb"] + +[node name="GeneralTab" type="HBoxContainer" node_paths=PackedStringArray("initial_focus")] +editor_description = "UI-48, UIFUN-45" +alignment = 1 +script = ExtResource("1_gbutn") +initial_focus = NodePath("VBoxContainer/GridContainer/SavegameFormatSelector") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="Control" type="Control" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.1 + +[node name="GridContainer" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +columns = 2 + +[node name="SavegameFormatLabel" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +text = "OPTIONS_GENERAL_SAVEFORMAT" + +[node name="SavegameFormatSelector" type="OptionButton" parent="VBoxContainer/GridContainer"] +editor_description = "UI-50" +layout_mode = 2 +focus_neighbor_bottom = NodePath("../AutosaveIntervalSelector") +item_count = 2 +selected = 0 +popup/item_0/text = "OPTIONS_GENERAL_BINARY" +popup/item_0/id = 0 +popup/item_1/text = "OPTIONS_GENERAL_TEXT" +popup/item_1/id = 1 +script = ExtResource("2_msx2u") +section_name = "general" +setting_name = "savegame_format" +default_selected = 0 + +[node name="AutosaveIntervalLabel" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +text = "OPTIONS_GENERAL_AUTOSAVE" + +[node name="AutosaveIntervalSelector" type="OptionButton" parent="VBoxContainer/GridContainer"] +editor_description = "UI-15, UIFUN-19" +layout_mode = 2 +focus_neighbor_top = NodePath("../SavegameFormatSelector") +focus_neighbor_bottom = NodePath("../LocaleButton") +item_count = 5 +selected = 0 +popup/item_0/text = "OPTIONS_GENERAL_AUTOSAVE_MONTHLY" +popup/item_0/id = 0 +popup/item_1/text = "OPTIONS_GENERAL_AUTOSAVE_BIMONTHLY" +popup/item_1/id = 1 +popup/item_2/text = "OPTIONS_GENERAL_AUTOSAVE_YEARLY" +popup/item_2/id = 2 +popup/item_3/text = "OPTIONS_GENERAL_AUTOSAVE_BIYEARLY" +popup/item_3/id = 3 +popup/item_4/text = "OPTIONS_GENERAL_AUTOSAVE_NEVER" +popup/item_4/id = 4 +script = ExtResource("2_t06tb") +section_name = "general" +setting_name = "autosave_interval" +default_selected = 0 + +[node name="LocaleLabel" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +text = "OPTIONS_GENERAL_LANGUAGE" + +[node name="LocaleButton" parent="VBoxContainer/GridContainer" instance=ExtResource("2_5cfd7")] +editor_description = "UI-79" +layout_mode = 2 +focus_neighbor_top = NodePath("../AutosaveIntervalSelector") +alignment = 0 +text_overrun_behavior = 4 diff --git a/game/src/Game/Menu/OptionMenu/GuiScaleSelector.gd b/game/src/Game/Menu/OptionMenu/GuiScaleSelector.gd new file mode 100644 index 0000000..4dd86e1 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/GuiScaleSelector.gd @@ -0,0 +1,64 @@ +extends SettingOptionButton + +# REQUIREMENTS +# * UIFUN-24 +# * UIFUN-31 + +@export +var default_value : float = GuiScale.error_guiscale + +func _find_guiscale_index_by_value(value : float) -> int: + for item_index in item_count: + if get_item_metadata(item_index) == value: + return item_index + return -1 + +func _sync_guiscales(to_select : float = GuiScale.get_current_guiscale()) -> void: + clear() + default_selected = -1 + selected = -1 + for guiscale_value in GuiScale.get_guiscale_value_list(): + add_item(GuiScale.get_guiscale_display_name(guiscale_value)) + set_item_metadata(item_count - 1, guiscale_value) + + if guiscale_value == default_value: + default_selected = item_count - 1 + + if guiscale_value == to_select: + selected = item_count - 1 + + if default_selected == -1: + default_selected = item_count - 1 + + if selected == -1: + selected = default_selected + +func _setup_button(): + if default_value <= 0: + default_value = ProjectSettings.get_setting("display/window/stretch/scale") + GuiScale.add_guiscale(default_value, &"default") + _sync_guiscales() + +func _get_value_for_file(select_value : int): + if _valid_index(select_value): + return get_item_metadata(select_value) + else: + return null + +func _set_value_from_file(load_value): + if typeof(load_value) == TYPE_FLOAT: + var target_guiscale : float = load_value + selected = _find_guiscale_index_by_value(target_guiscale) + if selected != -1: return + if GuiScale.add_guiscale(target_guiscale): + _sync_guiscales(target_guiscale) + return + push_error("Setting value '%s' invalid for setting [%s] %s" % [load_value, section_name, setting_name]) + selected = default_selected + +func _on_option_selected(index : int, by_user : bool): + if _valid_index(index): + GuiScale.set_guiscale(get_item_metadata(index)) + else: + push_error("Invalid GuiScaleSelector index: %d" % index) + reset_setting(not by_user) diff --git a/game/src/Game/Menu/OptionMenu/MonitorDisplaySelector.gd b/game/src/Game/Menu/OptionMenu/MonitorDisplaySelector.gd new file mode 100644 index 0000000..7de033a --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/MonitorDisplaySelector.gd @@ -0,0 +1,18 @@ +extends SettingOptionButton + +func _setup_button() -> void: + clear() + for screen_index in DisplayServer.get_screen_count(): + add_item("Monitor %d" % (screen_index + 1)) + default_selected = get_viewport().get_window().current_screen + +func _on_option_selected(index : int, by_user : bool) -> void: + if _valid_index(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 + else: + push_error("Invalid MonitorDisplaySelector index: %d" % index) + reset_setting(not by_user) diff --git a/game/src/Game/Menu/OptionMenu/OptionsMenu.gd b/game/src/Game/Menu/OptionMenu/OptionsMenu.gd new file mode 100644 index 0000000..5f6a088 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/OptionsMenu.gd @@ -0,0 +1,68 @@ +extends Control + +# REQUIREMENTS +# * SS-13 + +signal back_button_pressed + +func _ready(): + # Prepare options menu before loading user settings + 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) + + # REQUIREMENTS + # * UI-12 + # * UIFUN-14 + var reset_button := Button.new() + reset_button.text = "OPTIONS_RESET" + reset_button.pressed.connect(Events.Options.try_reset_settings) + button_list.add_child(reset_button) + + # REQUIREMENTS + # * UI-11 + # * UIFUN-17 + var back_button := Button.new() + back_button.text = "OPTIONS_BACK" + 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) + _save_overrides.call_deferred() + Events.Options.save_settings.connect(func(_f): self._save_overrides.call_deferred()) + +func _notification(what): + match what: + NOTIFICATION_CRASH: + _on_window_close_requested() + +func _input(event): + if self.is_visible_in_tree(): + if event.is_action_pressed("ui_cancel"): + _on_back_button_pressed() + +func _on_back_button_pressed(): + Events.Options.save_settings_to_file() + back_button_pressed.emit() + +func _on_window_close_requested() -> void: + if visible: + Events.Options.save_settings_to_file() + +func _save_overrides() -> void: + var override_path : String = ProjectSettings.get_setting("application/config/project_settings_override", "") + if override_path.is_empty(): + override_path = ProjectSettings.get_setting(Events.Options.settings_file_path_setting, Events.Options.settings_file_path_default) + var file := ConfigFile.new() + var err_ret := file.load(override_path) + if err_ret != OK: push_error("Failed to load overrides from %s" % override_path) + file.set_value("display", "window/size/mode", get_viewport().get_window().mode) + var resolution : Vector2i = Resolution.get_current_resolution() + file.set_value("display", "window/size/viewport_width", resolution.x) + file.set_value("display", "window/size/viewport_height", resolution.y) + err_ret = file.save(override_path) + if err_ret != OK: push_error("Failed to save overrides to %s" % override_path) diff --git a/game/src/Game/Menu/OptionMenu/OptionsMenu.tscn b/game/src/Game/Menu/OptionMenu/OptionsMenu.tscn new file mode 100644 index 0000000..3156e33 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/OptionsMenu.tscn @@ -0,0 +1,52 @@ +[gd_scene load_steps=8 format=3 uid="uid://cnbfxjy1m6wja"] + +[ext_resource type="Theme" uid="uid://fbxssqcg1s0m" path="res://theme/options_menu.tres" id="1_0up1d"] +[ext_resource type="Script" path="res://src/OptionMenu/OptionsMenu.gd" id="1_tlein"] +[ext_resource type="PackedScene" uid="uid://bq3awxxjn1tuw" path="res://src/OptionMenu/VideoTab.tscn" id="2_ji8xr"] +[ext_resource type="PackedScene" uid="uid://cbtgwpx2wxi33" path="res://src/OptionMenu/SoundTab.tscn" id="3_4w35t"] +[ext_resource type="PackedScene" uid="uid://duwjal7sd7p6w" path="res://src/OptionMenu/GeneralTab.tscn" id="3_6gvf6"] +[ext_resource type="PackedScene" uid="uid://bq7ibhm0txl5p" path="res://addons/keychain/ShortcutEdit.tscn" id="4_vdhjp"] +[ext_resource type="PackedScene" uid="uid://dp2grvybtecqu" path="res://src/OptionMenu/OtherTab.tscn" id="5_ahefp"] + +[node name="OptionsMenu" type="PanelContainer"] +editor_description = "UI-25" +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_0up1d") +theme_type_variation = &"BackgroundPanel" +script = ExtResource("1_tlein") + +[node name="Margin" type="MarginContainer" parent="."] +layout_mode = 2 +theme_type_variation = &"TabMargin" + +[node name="Tab" type="TabContainer" parent="Margin"] +editor_description = "UI-45" +layout_mode = 2 +tab_alignment = 1 +use_hidden_tabs_for_min_size = true + +[node name="General" parent="Margin/Tab" instance=ExtResource("3_6gvf6")] +layout_mode = 2 + +[node name="Video" parent="Margin/Tab" instance=ExtResource("2_ji8xr")] +editor_description = "UI-46, UIFUN-43" +visible = false +layout_mode = 2 + +[node name="Sound" parent="Margin/Tab" instance=ExtResource("3_4w35t")] +editor_description = "UI-47, UIFUN-44" +visible = false +layout_mode = 2 + +[node name="Controls" parent="Margin/Tab" instance=ExtResource("4_vdhjp")] +editor_description = "SS-27, UI-49, UIFUN-46" +visible = false +layout_mode = 2 +alignment = 1 + +[node name="Other" parent="Margin/Tab" instance=ExtResource("5_ahefp")] +layout_mode = 2 diff --git a/game/src/Game/Menu/OptionMenu/OtherTab.tscn b/game/src/Game/Menu/OptionMenu/OtherTab.tscn new file mode 100644 index 0000000..0ffc92d --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/OtherTab.tscn @@ -0,0 +1,18 @@ +[gd_scene format=3 uid="uid://dp2grvybtecqu"] + +[node name="Other" type="Control"] +visible = false +layout_mode = 3 +anchors_preset = 0 + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 + +[node name="Label" type="Label" parent="HBoxContainer"] +layout_mode = 2 +text = "Spinbox Example :)" + +[node name="SpinBox" type="SpinBox" parent="HBoxContainer"] +layout_mode = 2 diff --git a/game/src/Game/Menu/OptionMenu/QualityPresetSelector.gd b/game/src/Game/Menu/OptionMenu/QualityPresetSelector.gd new file mode 100644 index 0000000..57ba4ab --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/QualityPresetSelector.gd @@ -0,0 +1,4 @@ +extends SettingOptionButton + +func _setup_button(): + pass diff --git a/game/src/Game/Menu/OptionMenu/RefreshRateSelector.gd b/game/src/Game/Menu/OptionMenu/RefreshRateSelector.gd new file mode 100644 index 0000000..31b115b --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/RefreshRateSelector.gd @@ -0,0 +1,5 @@ +extends SettingOptionButton + + +func _setup_button(): + pass diff --git a/game/src/Game/Menu/OptionMenu/ResolutionRevertDialog.gd b/game/src/Game/Menu/OptionMenu/ResolutionRevertDialog.gd new file mode 100644 index 0000000..4d2b8f2 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/ResolutionRevertDialog.gd @@ -0,0 +1,35 @@ +extends ConfirmationDialog +class_name ResolutionRevertDialog + +signal dialog_accepted(button : SettingRevertButton) +signal dialog_reverted(button : SettingRevertButton) + +@export_group("Nodes") +@export var timer : Timer + +var _revert_node : SettingRevertButton = null + +func show_dialog(button : SettingRevertButton, time : float = 0) -> void: + timer.start(time) + popup_centered(Vector2(1,1)) + _revert_node = button + +func _notification(what): + if what == NOTIFICATION_VISIBILITY_CHANGED: + set_process(visible) + if not visible: _revert_node = null + +func _process(_delta) -> void: + dialog_text = tr("OPTIONS_VIDEO_RESOLUTION_DIALOG_TEXT").format({ "time": int(timer.time_left) }) + +func _on_canceled_or_close_requested() -> void: + timer.stop() + dialog_reverted.emit(_revert_node) + +func _on_confirmed() -> void: + timer.stop() + dialog_accepted.emit(_revert_node) + +func _on_resolution_revert_timer_timeout() -> void: + dialog_reverted.emit(_revert_node) + hide() diff --git a/game/src/Game/Menu/OptionMenu/ResolutionSelector.gd b/game/src/Game/Menu/OptionMenu/ResolutionSelector.gd new file mode 100644 index 0000000..ebdf718 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/ResolutionSelector.gd @@ -0,0 +1,91 @@ +extends SettingRevertButton + +# REQUIREMENTS +# * UIFUN-21 +# * UIFUN-28 +# * UIFUN-301 +# * UIFUN-302 + +@export var default_value : Vector2i = Resolution.error_resolution + +func _find_resolution_index_by_value(value : Vector2i) -> int: + for item_index in item_count: + if get_item_metadata(item_index) == value: + return item_index + return -1 + +func _sync_resolutions( + value : Vector2i = Resolution.error_resolution, + _resolution_name = null, + _resolution_display_name = null +) -> void: + clear() + default_selected = -1 + selected = -1 + var resolution_list := Resolution.get_resolution_value_list() + if value != Resolution.error_resolution: + resolution_list.append(value) + for resolution_value in resolution_list: + var display_name := "%sx%s" % [resolution_value.x, resolution_value.y] + var resolution_name := Resolution.get_resolution_name(resolution_value) + if resolution_name == &"Default": + display_name = "Default (%s)" % resolution_name + if not resolution_name.is_empty(): + display_name = "%s (%s)" % [display_name, resolution_name + (", Default" if resolution_value == default_value else "")] + add_item(display_name) + set_item_metadata(item_count - 1, resolution_value) + + if resolution_value == default_value: + default_selected = item_count - 1 + + if resolution_value == Resolution.get_current_resolution(): + selected = item_count - 1 + + if default_selected == -1: + default_selected = item_count - 1 + + if selected == -1: + selected = default_selected + +func _setup_button() -> void: + Resolution.resolution_added.connect(_sync_resolutions) + if default_value.x <= 0: + default_value.x = ProjectSettings.get_setting("display/window/size/viewport_width") + if default_value.y <= 0: + default_value.y = ProjectSettings.get_setting("display/window/size/viewport_height") + if not Resolution.has_resolution(default_value): + Resolution.add_resolution(default_value, &"Default") + else: + _sync_resolutions() + +func _get_value_for_file(select_value : int) -> Variant: + if _valid_index(select_value): + return get_item_metadata(select_value) + else: + return null + +func _set_value_from_file(load_value) -> void: + var target_resolution := Resolution.error_resolution + match typeof(load_value): + TYPE_VECTOR2I: target_resolution = load_value + TYPE_STRING, TYPE_STRING_NAME: target_resolution = Resolution.get_resolution_value_from_string(load_value) + if target_resolution != Resolution.error_resolution: + selected = _find_resolution_index_by_value(target_resolution) + if selected != -1: return + if Resolution.add_resolution(target_resolution): + Resolution.set_resolution(target_resolution) + return + push_error("Setting value '%s' invalid for setting [%s] %s" % [load_value, section_name, setting_name]) + selected = default_selected + +func _on_option_selected(index : int, by_user : bool) -> void: + if _valid_index(index): + if by_user: + print("Start Revert Countdown!") + revert_dialog.show_dialog.call_deferred(self) + previous_index = _find_resolution_index_by_value(Resolution.get_current_resolution()) + + Resolution.set_resolution(get_item_metadata(index)) + else: + push_error("Invalid ResolutionSelector index: %d" % index) + reset_setting(not by_user) diff --git a/game/src/Game/Menu/OptionMenu/ScreenModeSelector.gd b/game/src/Game/Menu/OptionMenu/ScreenModeSelector.gd new file mode 100644 index 0000000..af95901 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/ScreenModeSelector.gd @@ -0,0 +1,48 @@ +extends SettingRevertButton + +# REQUIREMENTS +# * UIFUN-42 + +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 _setup_button(): + default_selected = get_screen_mode_from_window_mode(get_viewport().get_window().mode) + selected = default_selected + +func _on_option_selected(index : int, by_user : bool) -> void: + if _valid_index(index): + if by_user: + print("Start Revert Countdown!") + revert_dialog.show_dialog.call_deferred(self) + previous_index = get_screen_mode_from_window_mode(get_viewport().get_window().mode) + + var current_resolution := Resolution.get_current_resolution() + var window_mode := get_window_mode_from_screen_mode(index) + Resolution.window_mode_changed.emit(window_mode) + get_viewport().get_window().mode = window_mode + Resolution.set_resolution(current_resolution) + else: + push_error("Invalid ScreenModeSelector index: %d" % index) + reset_setting(not by_user) diff --git a/game/src/Game/Menu/OptionMenu/SettingNodes/SettingHSlider.gd b/game/src/Game/Menu/OptionMenu/SettingNodes/SettingHSlider.gd new file mode 100644 index 0000000..6fa30ed --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/SettingNodes/SettingHSlider.gd @@ -0,0 +1,41 @@ +extends HSlider +class_name SettingHSlider + +@export +var section_name : String = "setting" + +@export +var setting_name : String = "setting_hslider" + +@export +var default_value : float = 0 + +func _ready(): + Events.Options.load_settings.connect(load_setting) + Events.Options.save_settings.connect(save_setting) + Events.Options.reset_settings.connect(reset_setting) + +func load_setting(file : ConfigFile): + if file == null: return + var load_value = file.get_value(section_name, setting_name, default_value) + match typeof(load_value): + TYPE_FLOAT, TYPE_INT: + if value == load_value: value_changed.emit(value) + value = load_value + return + TYPE_STRING, TYPE_STRING_NAME: + var load_string := load_value as String + if load_string.is_valid_float(): + load_value = load_string.to_float() + if value == load_value: value_changed.emit(value) + value = load_value + return + push_error("Setting value '%s' invalid for setting [%s] \"%s\"" % [load_value, section_name, setting_name]) + value = default_value + +func save_setting(file : ConfigFile): + if file == null: return + file.set_value(section_name, setting_name, value) + +func reset_setting(): + value = default_value diff --git a/game/src/Game/Menu/OptionMenu/SettingNodes/SettingOptionButton.gd b/game/src/Game/Menu/OptionMenu/SettingNodes/SettingOptionButton.gd new file mode 100644 index 0000000..c5a805e --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/SettingNodes/SettingOptionButton.gd @@ -0,0 +1,77 @@ +extends OptionButton +class_name SettingOptionButton + +signal option_selected(index : int, by_user : bool) + +@export +var section_name : String = "setting" + +@export +var setting_name : String = "setting_optionbutton" + +@export +var default_selected : int = -1: + get: return default_selected + set(v): + if v < 0 or item_count == 0: + default_selected = -1 + return + default_selected = v % item_count + +func _valid_index(index : int) -> bool: + return 0 <= index and index < item_count + +func _get_value_for_file(select_value : int): + if _valid_index(select_value): + return select_value + else: + return null + +func _set_value_from_file(load_value) -> void: + match typeof(load_value): + TYPE_INT: + if _valid_index(load_value): + selected = load_value + return + TYPE_STRING, TYPE_STRING_NAME: + var load_string := load_value as String + if load_string.is_valid_int(): + var load_int := load_string.to_int() + if _valid_index(load_int): + selected = load_int + return + for item_index in item_count: + if load_string == get_item_text(item_index): + selected = item_index + return + push_error("Setting value '%s' invalid for setting [%s] \"%s\"" % [load_value, section_name, setting_name]) + selected = default_selected + +func _setup_button() -> void: + pass + +func _ready(): + Events.Options.load_settings.connect(load_setting) + Events.Options.save_settings.connect(save_setting) + Events.Options.reset_settings.connect(reset_setting) + item_selected.connect(func(index : int): option_selected.emit(index, true)) + _setup_button() + if not _valid_index(default_selected) or selected == -1: + var msg := "Failed to generate %s %s options." % [setting_name, section_name] + push_error(msg) + OS.alert(msg, "%s Options Error" % section_name) + get_tree().quit() + +func load_setting(file : ConfigFile) -> void: + if file == null: return + _set_value_from_file(file.get_value(section_name, setting_name, _get_value_for_file(default_selected))) + option_selected.emit(selected, false) + +func save_setting(file : ConfigFile) -> void: + if file == null: return + file.set_value(section_name, setting_name, _get_value_for_file(selected)) + +func reset_setting(no_emit : bool = false) -> void: + selected = default_selected + if not no_emit: + option_selected.emit(selected, false) diff --git a/game/src/Game/Menu/OptionMenu/SettingNodes/SettingRevertButton.gd b/game/src/Game/Menu/OptionMenu/SettingNodes/SettingRevertButton.gd new file mode 100644 index 0000000..945d35b --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/SettingNodes/SettingRevertButton.gd @@ -0,0 +1,27 @@ +extends SettingOptionButton +class_name SettingRevertButton + +@export_group("Nodes") +@export var revert_dialog : ResolutionRevertDialog + +var previous_index : int = -1 + +func _ready(): + super() + if revert_dialog != null: + revert_dialog.visibility_changed.connect(_on_revert_dialog_visibility_changed) + revert_dialog.dialog_accepted.connect(_on_accepted) + revert_dialog.dialog_reverted.connect(_on_reverted) + +func _on_revert_dialog_visibility_changed() -> void: + disabled = revert_dialog.visible + if not revert_dialog.visible: + previous_index = -1 + +func _on_reverted(button : SettingRevertButton) -> void: + if button != self: return + selected = previous_index + option_selected.emit(selected, false) + +func _on_accepted(button : SettingRevertButton) -> void: + if button != self: return diff --git a/game/src/Game/Menu/OptionMenu/SoundTab.gd b/game/src/Game/Menu/OptionMenu/SoundTab.gd new file mode 100644 index 0000000..c707605 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/SoundTab.gd @@ -0,0 +1,4 @@ +extends HBoxContainer + +func _on_ear_exploder_toggled(button_pressed): + print("KABOOM!!!" if button_pressed else "DEFUSED!!!") diff --git a/game/src/Game/Menu/OptionMenu/SoundTab.tscn b/game/src/Game/Menu/OptionMenu/SoundTab.tscn new file mode 100644 index 0000000..10d7f10 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/SoundTab.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=3 format=3 uid="uid://cbtgwpx2wxi33"] + +[ext_resource type="Script" path="res://src/OptionMenu/SoundTab.gd" id="1_a7k0s"] +[ext_resource type="PackedScene" uid="uid://dy4si8comamnv" path="res://src/OptionMenu/VolumeGrid.tscn" id="1_okpft"] + +[node name="Sound" type="HBoxContainer"] +alignment = 1 +script = ExtResource("1_a7k0s") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="Control" type="Control" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.1 + +[node name="VolumeGrid" parent="VBoxContainer" instance=ExtResource("1_okpft")] +layout_mode = 2 + +[node name="ButtonGrid" type="GridContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 2 +columns = 2 + +[node name="Spacer" type="Control" parent="VBoxContainer/ButtonGrid"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="EarExploder" type="CheckButton" parent="VBoxContainer/ButtonGrid"] +layout_mode = 2 +text = "Explode Eardrums on Startup?" + +[connection signal="toggled" from="VBoxContainer/ButtonGrid/EarExploder" to="." method="_on_ear_exploder_toggled"] diff --git a/game/src/Game/Menu/OptionMenu/VideoTab.gd b/game/src/Game/Menu/OptionMenu/VideoTab.gd new file mode 100644 index 0000000..3d98678 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/VideoTab.gd @@ -0,0 +1,9 @@ +extends HBoxContainer + +@export var initial_focus: Control + +func _notification(what : int) -> void: + match(what): + NOTIFICATION_VISIBILITY_CHANGED: + if visible and is_inside_tree(): + initial_focus.grab_focus() diff --git a/game/src/Game/Menu/OptionMenu/VideoTab.tscn b/game/src/Game/Menu/OptionMenu/VideoTab.tscn new file mode 100644 index 0000000..244f481 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/VideoTab.tscn @@ -0,0 +1,181 @@ +[gd_scene load_steps=9 format=3 uid="uid://bq3awxxjn1tuw"] + +[ext_resource type="Script" path="res://src/OptionMenu/ResolutionSelector.gd" id="1_i8nro"] +[ext_resource type="Script" path="res://src/OptionMenu/VideoTab.gd" id="1_jvv62"] +[ext_resource type="Script" path="res://src/OptionMenu/ScreenModeSelector.gd" id="2_wa7vw"] +[ext_resource type="Script" path="res://src/OptionMenu/GuiScaleSelector.gd" id="3_pgc5d"] +[ext_resource type="Script" path="res://src/OptionMenu/MonitorDisplaySelector.gd" id="3_y6lyb"] +[ext_resource type="Script" path="res://src/OptionMenu/RefreshRateSelector.gd" id="4_381mg"] +[ext_resource type="Script" path="res://src/OptionMenu/QualityPresetSelector.gd" id="5_srg4v"] +[ext_resource type="Script" path="res://src/OptionMenu/ResolutionRevertDialog.gd" id="8_802cr"] + +[node name="Video" type="HBoxContainer" node_paths=PackedStringArray("initial_focus")] +editor_description = "UI-46" +alignment = 1 +script = ExtResource("1_jvv62") +initial_focus = NodePath("VideoSettingList/VideoSettingGrid/ResolutionSelector") + +[node name="VideoSettingList" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="Control" type="Control" parent="VideoSettingList"] +layout_mode = 2 +size_flags_vertical = 3 +size_flags_stretch_ratio = 0.1 + +[node name="VideoSettingGrid" type="GridContainer" parent="VideoSettingList"] +layout_mode = 2 +size_flags_vertical = 3 +columns = 2 + +[node name="ResolutionLabel" type="Label" parent="VideoSettingList/VideoSettingGrid"] +layout_mode = 2 +text = "OPTIONS_VIDEO_RESOLUTION" + +[node name="ResolutionSelector" type="OptionButton" parent="VideoSettingList/VideoSettingGrid" node_paths=PackedStringArray("revert_dialog")] +editor_description = "UI-19" +layout_mode = 2 +focus_neighbor_bottom = NodePath("../ScreenModeSelector") +item_count = 1 +selected = 0 +popup/item_0/text = "MISSING" +popup/item_0/id = 0 +script = ExtResource("1_i8nro") +revert_dialog = NodePath("../../../ResolutionRevertDialog") +section_name = "video" +setting_name = "resolution" + +[node name="GuiScaleLabel" type="Label" parent="VideoSettingList/VideoSettingGrid"] +layout_mode = 2 +text = "OPTIONS_VIDEO_GUI_SCALE" + +[node name="GuiScaleSelector" type="OptionButton" parent="VideoSettingList/VideoSettingGrid"] +editor_description = "UI-23" +layout_mode = 2 +focus_neighbor_bottom = NodePath("../ScreenModeSelector") +item_count = 1 +selected = 0 +popup/item_0/text = "MISSING" +popup/item_0/id = 0 +script = ExtResource("3_pgc5d") +section_name = "video" +setting_name = "gui_scale" + +[node name="ScreenModeLabel" type="Label" parent="VideoSettingList/VideoSettingGrid"] +editor_description = "UI-44" +layout_mode = 2 +text = "OPTIONS_VIDEO_SCREEN_MODE" + +[node name="ScreenModeSelector" type="OptionButton" parent="VideoSettingList/VideoSettingGrid" node_paths=PackedStringArray("revert_dialog")] +layout_mode = 2 +focus_neighbor_top = NodePath("../ResolutionSelector") +focus_neighbor_bottom = NodePath("../MonitorDisplaySelector") +item_count = 3 +selected = 0 +popup/item_0/text = "OPTIONS_VIDEO_FULLSCREEN" +popup/item_0/id = 0 +popup/item_1/text = "OPTIONS_VIDEO_BORDERLESS" +popup/item_1/id = 1 +popup/item_2/text = "OPTIONS_VIDEO_WINDOWED" +popup/item_2/id = 2 +script = ExtResource("2_wa7vw") +revert_dialog = NodePath("../../../ResolutionRevertDialog") +section_name = "video" +setting_name = "mode_selected" + +[node name="MonitorSelectionLabel" type="Label" parent="VideoSettingList/VideoSettingGrid"] +layout_mode = 2 +text = "OPTIONS_VIDEO_MONITOR_SELECTION" + +[node name="MonitorDisplaySelector" type="OptionButton" parent="VideoSettingList/VideoSettingGrid"] +layout_mode = 2 +focus_neighbor_top = NodePath("../ScreenModeSelector") +focus_neighbor_bottom = NodePath("../RefreshRateSelector") +item_count = 1 +selected = 0 +popup/item_0/text = "MISSING" +popup/item_0/id = 0 +script = ExtResource("3_y6lyb") +section_name = "video" +setting_name = "current_screen" + +[node name="RefreshRateLabel" type="Label" parent="VideoSettingList/VideoSettingGrid"] +layout_mode = 2 +text = "OPTIONS_VIDEO_REFRESH_RATE" + +[node name="RefreshRateSelector" type="OptionButton" parent="VideoSettingList/VideoSettingGrid"] +editor_description = "UI-18, UIFUN-20" +layout_mode = 2 +tooltip_text = "OPTIONS_VIDEO_REFRESH_RATE_TOOLTIP" +focus_neighbor_top = NodePath("../MonitorDisplaySelector") +focus_neighbor_bottom = NodePath("../QualityPresetSelector") +item_count = 8 +selected = 0 +popup/item_0/text = "VSYNC" +popup/item_0/id = 0 +popup/item_1/text = "30hz" +popup/item_1/id = 1 +popup/item_2/text = "60hz" +popup/item_2/id = 2 +popup/item_3/text = "90hz" +popup/item_3/id = 3 +popup/item_4/text = "120hz" +popup/item_4/id = 4 +popup/item_5/text = "144hz" +popup/item_5/id = 5 +popup/item_6/text = "365hz" +popup/item_6/id = 6 +popup/item_7/text = "Unlimited" +popup/item_7/id = 7 +script = ExtResource("4_381mg") +section_name = "video" +setting_name = "refresh_rate" +default_selected = 0 + +[node name="QualityPresetLabel" type="Label" parent="VideoSettingList/VideoSettingGrid"] +layout_mode = 2 +text = "OPTIONS_VIDEO_QUALITY" + +[node name="QualityPresetSelector" type="OptionButton" parent="VideoSettingList/VideoSettingGrid"] +editor_description = "UI-21, UIFUN-22" +layout_mode = 2 +focus_neighbor_top = NodePath("../RefreshRateSelector") +item_count = 5 +selected = 1 +popup/item_0/text = "Low" +popup/item_0/id = 0 +popup/item_1/text = "Medium" +popup/item_1/id = 1 +popup/item_2/text = "High" +popup/item_2/id = 2 +popup/item_3/text = "Ultra" +popup/item_3/id = 3 +popup/item_4/text = "Custom" +popup/item_4/id = 4 +script = ExtResource("5_srg4v") +section_name = "video" +setting_name = "quality_preset" +default_selected = 1 + +[node name="ResolutionRevertDialog" type="ConfirmationDialog" parent="." node_paths=PackedStringArray("timer")] +editor_description = "UI-873" +disable_3d = true +title = "OPTIONS_VIDEO_RESOLUTION_DIALOG_TITLE" +size = Vector2i(730, 100) +ok_button_text = "DIALOG_OK" +cancel_button_text = "DIALOG_CANCEL" +script = ExtResource("8_802cr") +timer = NodePath("ResolutionRevertTimer") + +[node name="ResolutionRevertTimer" type="Timer" parent="ResolutionRevertDialog"] +wait_time = 5.0 +one_shot = true + +[connection signal="option_selected" from="VideoSettingList/VideoSettingGrid/ResolutionSelector" to="VideoSettingList/VideoSettingGrid/ResolutionSelector" method="_on_option_selected"] +[connection signal="option_selected" from="VideoSettingList/VideoSettingGrid/GuiScaleSelector" to="VideoSettingList/VideoSettingGrid/GuiScaleSelector" method="_on_option_selected"] +[connection signal="option_selected" from="VideoSettingList/VideoSettingGrid/ScreenModeSelector" to="VideoSettingList/VideoSettingGrid/ScreenModeSelector" method="_on_option_selected"] +[connection signal="option_selected" from="VideoSettingList/VideoSettingGrid/MonitorDisplaySelector" to="VideoSettingList/VideoSettingGrid/MonitorDisplaySelector" method="_on_option_selected"] +[connection signal="canceled" from="ResolutionRevertDialog" to="ResolutionRevertDialog" method="_on_canceled_or_close_requested"] +[connection signal="close_requested" from="ResolutionRevertDialog" to="ResolutionRevertDialog" method="_on_canceled_or_close_requested"] +[connection signal="confirmed" from="ResolutionRevertDialog" to="ResolutionRevertDialog" method="_on_confirmed"] +[connection signal="timeout" from="ResolutionRevertDialog/ResolutionRevertTimer" to="ResolutionRevertDialog" method="_on_resolution_revert_timer_timeout"] diff --git a/game/src/Game/Menu/OptionMenu/VolumeGrid.gd b/game/src/Game/Menu/OptionMenu/VolumeGrid.gd new file mode 100644 index 0000000..46613b4 --- /dev/null +++ b/game/src/Game/Menu/OptionMenu/VolumeGrid.gd @@ -0,0 +1,70 @@ +extends GridContainer + +const RATIO_FOR_LINEAR : float = 100 + +var _slider_dictionary : Dictionary + +var initial_focus : Control + +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_row(bus_name : String, bus_index : int) -> HSlider: + var volume_label := Label.new() + if bus_name == "Master": + volume_label.text = "MASTER_BUS" + else: + volume_label.text = bus_name + 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 + if not initial_focus: initial_focus = volume_slider + return volume_slider + +# REQUIREMENTS +# * UI-22 +func _ready(): + for bus_index in AudioServer.bus_count: + add_volume_row(AudioServer.get_bus_name(bus_index), bus_index) + +func _notification(what : int) -> void: + match(what): + NOTIFICATION_VISIBILITY_CHANGED: + if visible and is_inside_tree() and initial_focus: initial_focus.grab_focus() + +# REQUIREMENTS +# * UIFUN-30 +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_slider in _slider_dictionary.values(): + volume_slider.load_setting(load_file) + +# REQUIREMENTS +# * UIFUN-23 +func _on_options_menu_save_settings(save_file : ConfigFile): + for volume_slider in _slider_dictionary.values(): + volume_slider.save_setting(save_file) + + +func _on_options_menu_reset_settings(): + for volume_slider in _slider_dictionary.values(): + volume_slider.reset_setting() diff --git a/game/src/Game/Menu/OptionMenu/VolumeGrid.tscn b/game/src/Game/Menu/OptionMenu/VolumeGrid.tscn new file mode 100644 index 0000000..6d4de3c --- /dev/null +++ b/game/src/Game/Menu/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") |