diff options
51 files changed, 2240 insertions, 200 deletions
diff --git a/game/addons/keychain/Keychain.gd b/game/addons/keychain/Keychain.gd new file mode 100644 index 0000000..ff939f3 --- /dev/null +++ b/game/addons/keychain/Keychain.gd @@ -0,0 +1,212 @@ +extends Node + +const TRANSLATIONS_PATH := "res://addons/keychain/translations" +const PROFILES_PATH := "user://shortcut_profiles" + +# Change these settings +var profiles := [preload("profiles/default.tres")] +var selected_profile: ShortcutProfile = profiles[0] +var profile_index := 0 +# Syntax: "action_name": InputAction.new("Action Display Name", "Group", true) +# Note that "action_name" must already exist in the Project's Input Map. +var actions := {} +# Syntax: "Group Name": InputGroup.new("Parent Group Name") +var groups := {} +var ignore_actions := [] +var ignore_ui_actions := true +var changeable_types := [true, true, true, true] +var multiple_menu_accelerators := false +var config_path := "user://cache.ini" +var config_file: ConfigFile + + +class InputAction: + var display_name := "" + var group := "" + var global := true + + func _init(_display_name := "",_group := "",_global := true): + display_name = _display_name + group = _group + global = _global + + func update_node(_action: String) -> void: + pass + + func handle_input(_event: InputEvent, _action: String) -> bool: + return false + + +# This class is useful for the accelerators of PopupMenu items +# It's possible for PopupMenu items to have multiple shortcuts by using +# set_item_shortcut(), but we have no control over the accelerator text that appears. +# Thus, we are stuck with using accelerators instead of shortcuts. +# If Godot ever receives the ability to change the accelerator text of the items, +# we could in theory remove this class. +# If you don't care about PopupMenus in the same scene as ShortcutEdit +# such as projects like Pixelorama where everything is in the same scene, +# then you can ignore this class. +class MenuInputAction: + extends InputAction + var node_path := "" + var node: PopupMenu + var menu_item_id := 0 + var echo := false + + func _init( + _display_name := "", + _group := "", + _global := true, + _node_path := "", + _menu_item_id := 0, + _echo := false + ) -> void: + super._init(_display_name, _group, _global) + node_path = _node_path + menu_item_id = _menu_item_id + echo = _echo + + func get_node(root: Node) -> void: + var temp_node = root.get_node(node_path) + if temp_node is PopupMenu: + node = node + elif temp_node is MenuButton: + node = temp_node.get_popup() + + func update_node(action: String) -> void: + if !node: + return + var first_key: InputEventKey = Keychain.action_get_first_key(action) + var accel := first_key.get_keycode_with_modifiers() if first_key else 0 + node.set_item_accelerator(menu_item_id, accel) + + func handle_input(event: InputEvent, action: String) -> bool: + if not node: + return false + if event.is_action_pressed(action, false, true): + if event is InputEventKey: + var acc: int = node.get_item_accelerator(menu_item_id) + # If the event is the same as the menu item's accelerator, skip + if acc == event.get_keycode_with_modifiers(): + return true + node.emit_signal("id_pressed", menu_item_id) + return true + if event.is_action(action, true) and echo: + if event.is_echo(): + node.emit_signal("id_pressed", menu_item_id) + return true + + return false + + +class InputGroup: + var parent_group := "" + var folded := true + var tree_item: TreeItem + + func _init(_parent_group := "", _folded := true) -> void: + parent_group = _parent_group + folded = _folded + + +func _ready() -> void: + if !config_file: + config_file = ConfigFile.new() + if !config_path.is_empty(): + config_file.load(config_path) + + set_process_input(multiple_menu_accelerators) + + # Load shortcut profiles + DirAccess.make_dir_recursive_absolute(PROFILES_PATH) + var profile_dir := DirAccess.open(PROFILES_PATH) + profile_dir.make_dir(PROFILES_PATH) + profile_dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 + var file_name = profile_dir.get_next() + while file_name != "": + if !profile_dir.current_is_dir(): + if file_name.get_extension() == "tres": + var file = load(PROFILES_PATH.path_join(file_name)) + if file is ShortcutProfile: + profiles.append(file) + file_name = profile_dir.get_next() + + # If there are no profiles besides the default, create one custom + if profiles.size() == 1: + var profile := ShortcutProfile.new() + profile.name = "Custom" + profile.resource_path = PROFILES_PATH.path_join("custom.tres") + var saved := profile.save() + if saved: + profiles.append(profile) + + for profile in profiles: + profile.fill_bindings() + + var l18n_dir := DirAccess.open(TRANSLATIONS_PATH) + l18n_dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 + file_name = l18n_dir.get_next() + while file_name != "": + if !l18n_dir.current_is_dir(): + if file_name.get_extension() == "po": + var t: Translation = load(TRANSLATIONS_PATH.path_join(file_name)) + TranslationServer.add_translation(t) + file_name = l18n_dir.get_next() + + profile_index = config_file.get_value("shortcuts", "shortcuts_profile", 0) + change_profile(profile_index) + + for action in actions: + var input_action: InputAction = actions[action] + if input_action is MenuInputAction: + input_action.get_node(get_tree().current_scene) + + +func _input(event: InputEvent) -> void: + if event is InputEventMouseMotion: + return + + for action in actions: + var input_action: InputAction = actions[action] + var done: bool = input_action.handle_input(event, action) + if done: + return + + +func change_profile(index: int) -> void: + if index >= profiles.size(): + index = profiles.size() - 1 + profile_index = index + selected_profile = profiles[index] + for action in selected_profile.bindings: + action_erase_events(action) + for event in selected_profile.bindings[action]: + action_add_event(action, event) + + +func action_add_event(action: String, event: InputEvent) -> void: + InputMap.action_add_event(action, event) + if action in actions: + actions[action].update_node(action) + + +func action_erase_event(action: String, event: InputEvent) -> void: + InputMap.action_erase_event(action, event) + if action in actions: + actions[action].update_node(action) + + +func action_erase_events(action: String) -> void: + InputMap.action_erase_events(action) + if action in actions: + actions[action].update_node(action) + + +func action_get_first_key(action: String) -> InputEventKey: + var first_key: InputEventKey = null + var events := InputMap.action_get_events(action) + for event in events: + if event is InputEventKey: + first_key = event + break + return first_key diff --git a/game/addons/keychain/LICENSE b/game/addons/keychain/LICENSE new file mode 100644 index 0000000..5deb9a7 --- /dev/null +++ b/game/addons/keychain/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Orama Interactive + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/game/addons/keychain/ShortcutEdit.gd b/game/addons/keychain/ShortcutEdit.gd new file mode 100644 index 0000000..fe4e69f --- /dev/null +++ b/game/addons/keychain/ShortcutEdit.gd @@ -0,0 +1,416 @@ +extends Control + +enum { KEYBOARD, MOUSE, JOY_BUTTON, JOY_AXIS } + +const MOUSE_BUTTON_NAMES := [ + "Left Button", + "Right Button", + "Middle Button", + "Wheel Up Button", + "Wheel Down Button", + "Wheel Left Button", + "Wheel Right Button", + "X Button 1", + "X Button 2", +] + +const JOY_BUTTON_NAMES := [ + "DualShock Cross, Xbox A, Nintendo B", + "DualShock Circle, Xbox B, Nintendo A", + "DualShock Square, Xbox X, Nintendo Y", + "DualShock Triangle, Xbox Y, Nintendo X", + "L, L1", + "R, R1", + "L2", + "R2", + "L3", + "R3", + "Select, DualShock Share, Nintendo -", + "Start, DualShock Options, Nintendo +", + "D-Pad Up", + "D-Pad Down", + "D-Pad Left", + "D-Pad Right", + "Home, DualShock PS, Guide", + "Xbox Share, PS5 Microphone, Nintendo Capture", + "Xbox Paddle 1", + "Xbox Paddle 2", + "Xbox Paddle 3", + "Xbox Paddle 4", + "PS4/5 Touchpad", +] + +const JOY_AXIS_NAMES := [ + "(Left Stick Left)", + "(Left Stick Right)", + "(Left Stick Up)", + "(Left Stick Down)", + "(Right Stick Left)", + "(Right Stick Right)", + "(Right Stick Up)", + "(Right Stick Down)", + "", + "", + "", + "", + "", + "(L2)", + "", + "(R2)", + "", + "", + "", + "", +] + +var currently_editing_tree_item: TreeItem +var is_editing := false +# Textures taken from Godot https://github.com/godotengine/godot/tree/master/editor/icons +var add_tex: Texture2D = preload("assets/add.svg") +var edit_tex: Texture2D = preload("assets/edit.svg") +var delete_tex: Texture2D = preload("assets/close.svg") +var joy_axis_tex: Texture2D = preload("assets/joy_axis.svg") +var joy_button_tex: Texture2D = preload("assets/joy_button.svg") +var key_tex: Texture2D = preload("assets/keyboard.svg") +var key_phys_tex: Texture2D = preload("assets/keyboard_physical.svg") +var mouse_tex: Texture2D = preload("assets/mouse.svg") +var shortcut_tex: Texture2D = preload("assets/shortcut.svg") +var folder_tex: Texture2D = preload("assets/folder.svg") + +@onready var tree: Tree = $VBoxContainer/ShortcutTree +@onready var profile_option_button: OptionButton = find_child("ProfileOptionButton") +@onready var rename_profile_button: Button = find_child("RenameProfile") +@onready var delete_profile_button: Button = find_child("DeleteProfile") +@onready var shortcut_type_menu: PopupMenu = $ShortcutTypeMenu +@onready var keyboard_shortcut_selector: ConfirmationDialog = $KeyboardShortcutSelectorDialog +@onready var mouse_shortcut_selector: ConfirmationDialog = $MouseShortcutSelectorDialog +@onready var joy_key_shortcut_selector: ConfirmationDialog = $JoyKeyShortcutSelectorDialog +@onready var joy_axis_shortcut_selector: ConfirmationDialog = $JoyAxisShortcutSelectorDialog +@onready var profile_settings: ConfirmationDialog = $ProfileSettings +@onready var profile_name: LineEdit = $ProfileSettings/ProfileName +@onready var delete_confirmation: ConfirmationDialog = $DeleteConfirmation + + +func _ready() -> void: + for profile in Keychain.profiles: + profile_option_button.add_item(profile.name) + + _fill_selector_options() + + # Remove input types that are not changeable + var i := 0 + for type in Keychain.changeable_types: + if !type: + shortcut_type_menu.remove_item(i) + else: + i += 1 + + profile_option_button.select(Keychain.profile_index) + _on_ProfileOptionButton_item_selected(Keychain.profile_index) + if OS.get_name() == "HTML5": + $VBoxContainer/HBoxContainer/OpenProfileFolder.queue_free() + + +func _construct_tree() -> void: + var buttons_disabled := false if Keychain.selected_profile.customizable else true + var tree_root: TreeItem = tree.create_item() + for group in Keychain.groups: # Create groups + var input_group: Keychain.InputGroup = Keychain.groups[group] + _create_group_tree_item(input_group, group) + + for action in InputMap.get_actions(): # Fill the tree with actions and their events + if action in Keychain.ignore_actions: + continue + if Keychain.ignore_ui_actions and (action as String).begins_with("ui_"): + continue + + var display_name := get_action_name(action) + var group_name := "" + if action in Keychain.actions: + var input_action: Keychain.InputAction = Keychain.actions[action] + group_name = input_action.group + + var tree_item: TreeItem + if not group_name.is_empty() and group_name in Keychain.groups: + var input_group: Keychain.InputGroup = Keychain.groups[group_name] + var group_root: TreeItem = input_group.tree_item + tree_item = tree.create_item(group_root) + + else: + tree_item = tree.create_item(tree_root) + + tree_item.set_text(0, display_name) + tree_item.set_metadata(0, action) + tree_item.set_icon(0, shortcut_tex) + for event in InputMap.action_get_events(action): + add_event_tree_item(event, tree_item) + + tree_item.add_button(0, add_tex, 0, buttons_disabled, "Add") + tree_item.add_button(0, delete_tex, 1, buttons_disabled, "Delete") + tree_item.collapsed = true + + +func _fill_selector_options() -> void: + keyboard_shortcut_selector.entered_shortcut.visible = true + keyboard_shortcut_selector.option_button.visible = false + mouse_shortcut_selector.input_type_l.text = "Mouse Button Index:" + joy_key_shortcut_selector.input_type_l.text = "Joypad Button Index:" + joy_axis_shortcut_selector.input_type_l.text = "Joypad Axis Index:" + + var mouse_option_button: OptionButton = mouse_shortcut_selector.option_button + for option in MOUSE_BUTTON_NAMES: + mouse_option_button.add_item(option) + + var joy_key_option_button: OptionButton = joy_key_shortcut_selector.option_button + for i in JOY_BUTTON_MAX: + var text: String = tr("Button") + " %s" % i + if i < JOY_BUTTON_NAMES.size(): + text += " (%s)" % tr(JOY_BUTTON_NAMES[i]) + joy_key_option_button.add_item(text) + + var joy_axis_option_button: OptionButton = joy_axis_shortcut_selector.option_button + var i := 0.0 + for option in JOY_AXIS_NAMES: + var sign_symbol = "+" if floor(i) != i else "-" + var text: String = tr("Axis") + " %s %s %s" % [floor(i), sign_symbol, tr(option)] + joy_axis_option_button.add_item(text) + i += 0.5 + + +func _create_group_tree_item(group: Keychain.InputGroup, group_name: String) -> void: + if group.tree_item: + return + + var group_root: TreeItem + if group.parent_group: + var parent_group: Keychain.InputGroup = Keychain.groups[group.parent_group] + _create_group_tree_item(parent_group, group.parent_group) + group_root = tree.create_item(parent_group.tree_item) + else: + group_root = tree.create_item(tree.get_root()) + group_root.set_text(0, group_name) + group_root.set_icon(0, folder_tex) + group.tree_item = group_root + if group.folded: + group_root.collapsed = true + + +func get_action_name(action: String) -> String: + var display_name := "" + if action in Keychain.actions: + display_name = Keychain.actions[action].display_name + + if display_name.is_empty(): + display_name = _humanize_snake_case(action) + return display_name + + +func _humanize_snake_case(text: String) -> String: + text = text.replace("_", " ") + var first_letter := text.left(1) + first_letter = first_letter.capitalize() + text = text.right(-1) + text = text.insert(0, first_letter) + return text + + +func add_event_tree_item(event: InputEvent, action_tree_item: TreeItem) -> void: + var event_class := event.get_class() + match event_class: + "InputEventKey": + if !Keychain.changeable_types[0]: + return + "InputEventMouseButton": + if !Keychain.changeable_types[1]: + return + "InputEventJoypadButton": + if !Keychain.changeable_types[2]: + return + "InputEventJoypadMotion": + if !Keychain.changeable_types[3]: + return + + var buttons_disabled := false if Keychain.selected_profile.customizable else true + var event_tree_item: TreeItem = tree.create_item(action_tree_item) + event_tree_item.set_text(0, event_to_str(event)) + event_tree_item.set_metadata(0, event) + match event_class: + "InputEventKey": + var scancode: int = event.get_keycode_with_modifiers() + if scancode > 0: + event_tree_item.set_icon(0, key_tex) + else: + event_tree_item.set_icon(0, key_phys_tex) + "InputEventMouseButton": + event_tree_item.set_icon(0, mouse_tex) + "InputEventJoypadButton": + event_tree_item.set_icon(0, joy_button_tex) + "InputEventJoypadMotion": + event_tree_item.set_icon(0, joy_axis_tex) + event_tree_item.add_button(0, edit_tex, 0, buttons_disabled, "Edit") + event_tree_item.add_button(0, delete_tex, 1, buttons_disabled, "Delete") + + +func event_to_str(event: InputEvent) -> String: + var output := "" + if event is InputEventKey: + var scancode: int = 0 + if event.keycode != 0: + scancode = event.get_keycode_with_modifiers() + var physical_str := "" + if scancode == 0: + scancode = event.get_physical_keycode_with_modifiers() + physical_str = " " + tr("(Physical)") + output = OS.get_keycode_string(scancode) + physical_str + + elif event is InputEventMouseButton: + output = tr(MOUSE_BUTTON_NAMES[event.button_index - 1]) + + elif event is InputEventJoypadButton: + var button_index: int = event.button_index + output = tr("Button") + if button_index >= JOY_BUTTON_NAMES.size(): + output += " %s" % button_index + else: + output += " %s (%s)" % [button_index, tr(JOY_BUTTON_NAMES[button_index])] + + elif event is InputEventJoypadMotion: + var positive_axis: bool = event.axis_value > 0 + var axis_value: int = event.axis * 2 + int(positive_axis) + var sign_symbol = "+" if positive_axis else "-" + output = tr("Axis") + output += " %s %s %s" % [event.axis, sign_symbol, tr(JOY_AXIS_NAMES[axis_value])] + return output + + +func _on_shortcut_tree_button_clicked(item: TreeItem, _column: int, id: int, _mbi: int) -> void: + var action = item.get_metadata(0) + currently_editing_tree_item = item + if action is StringName: + if id == 0: # Add + var rect: Rect2 = tree.get_item_area_rect(item, 0) + rect.position.x = rect.end.x - 42 + rect.position.y += 42 - tree.get_scroll().y + rect.position += global_position + rect.size = Vector2(110, 23 * shortcut_type_menu.get_item_count()) + shortcut_type_menu.popup(rect) + elif id == 1: # Delete + Keychain.action_erase_events(action) + Keychain.selected_profile.change_action(action) + for child in item.get_children(): + child.free() + + elif action is InputEvent: + var parent_action = item.get_parent().get_metadata(0) + if id == 0: # Edit + if action is InputEventKey: + keyboard_shortcut_selector.popup_centered() + elif action is InputEventMouseButton: + mouse_shortcut_selector.popup_centered() + elif action is InputEventJoypadButton: + joy_key_shortcut_selector.popup_centered() + elif action is InputEventJoypadMotion: + joy_axis_shortcut_selector.popup_centered() + elif id == 1: # Delete + if not parent_action is StringName: + return + Keychain.action_erase_event(parent_action, action) + Keychain.selected_profile.change_action(parent_action) + item.free() + + +func _on_ShortcutTree_item_activated() -> void: + var selected_item: TreeItem = tree.get_selected() + if selected_item.get_button_count(0) > 0 and !selected_item.is_button_disabled(0, 0): + _on_shortcut_tree_button_clicked(tree.get_selected(), 0, 0, 0) + + +func _on_ShortcutTypeMenu_id_pressed(id: int) -> void: + if id == KEYBOARD: + keyboard_shortcut_selector.popup_centered() + elif id == MOUSE: + mouse_shortcut_selector.popup_centered() + elif id == JOY_BUTTON: + joy_key_shortcut_selector.popup_centered() + elif id == JOY_AXIS: + joy_axis_shortcut_selector.popup_centered() + + +func _on_ProfileOptionButton_item_selected(index: int) -> void: + Keychain.change_profile(index) + rename_profile_button.disabled = false if Keychain.selected_profile.customizable else true + delete_profile_button.disabled = false if Keychain.selected_profile.customizable else true + + # Re-construct the tree + for group in Keychain.groups: + Keychain.groups[group].tree_item = null + tree.clear() + _construct_tree() + Keychain.config_file.set_value("shortcuts", "shortcuts_profile", index) + Keychain.config_file.save(Keychain.config_path) + + +func _on_NewProfile_pressed() -> void: + is_editing = false + profile_name.text = "New Shortcut Profile" + profile_settings.title = "New Shortcut Profile" + profile_settings.popup_centered() + + +func _on_RenameProfile_pressed() -> void: + is_editing = true + profile_name.text = Keychain.selected_profile.name + profile_settings.title = "Rename Shortcut Profile" + profile_settings.popup_centered() + + +func _on_DeleteProfile_pressed() -> void: + delete_confirmation.popup_centered() + + +func _on_OpenProfileFolder_pressed() -> void: + OS.shell_open(ProjectSettings.globalize_path(Keychain.PROFILES_PATH)) + + +func _on_ProfileSettings_confirmed() -> void: + var file_name := profile_name.text + ".tres" + var profile := ShortcutProfile.new() + profile.name = profile_name.text + profile.resource_path = Keychain.PROFILES_PATH.path_join(file_name) + profile.fill_bindings() + var saved := profile.save() + if not saved: + return + + if is_editing: + var old_file_name: String = Keychain.selected_profile.resource_path + if old_file_name != file_name: + _delete_profile_file(old_file_name) + Keychain.profiles[Keychain.profile_index] = profile + profile_option_button.set_item_text(Keychain.profile_index, profile.name) + else: # Add new shortcut profile + Keychain.profiles.append(profile) + profile_option_button.add_item(profile.name) + Keychain.profile_index = Keychain.profiles.size() - 1 + profile_option_button.select(Keychain.profile_index) + _on_ProfileOptionButton_item_selected(Keychain.profile_index) + + +func _delete_profile_file(file_name: String) -> void: + var dir := DirAccess.open(file_name.get_base_dir()) + var err := dir.get_open_error() + if err != OK: + print("Error deleting shortcut profile %s. Error code: %s" % [file_name, err]) + return + dir.remove(file_name) + + +func _on_DeleteConfirmation_confirmed() -> void: + _delete_profile_file(Keychain.selected_profile.resource_path) + profile_option_button.remove_item(Keychain.profile_index) + Keychain.profiles.remove_at(Keychain.profile_index) + Keychain.profile_index -= 1 + if Keychain.profile_index < 0: + Keychain.profile_index = 0 + profile_option_button.select(Keychain.profile_index) + _on_ProfileOptionButton_item_selected(Keychain.profile_index) diff --git a/game/addons/keychain/ShortcutEdit.tscn b/game/addons/keychain/ShortcutEdit.tscn new file mode 100644 index 0000000..daeb0b8 --- /dev/null +++ b/game/addons/keychain/ShortcutEdit.tscn @@ -0,0 +1,113 @@ +[gd_scene load_steps=7 format=3 uid="uid://bq7ibhm0txl5p"] + +[ext_resource type="Script" path="res://addons/keychain/ShortcutEdit.gd" id="1"] +[ext_resource type="Texture2D" uid="uid://ca58ufal2ufd8" path="res://addons/keychain/assets/joy_button.svg" id="2"] +[ext_resource type="Texture2D" uid="uid://c2s5rm4nec5yh" path="res://addons/keychain/assets/keyboard.svg" id="3"] +[ext_resource type="Texture2D" uid="uid://bb6q6om3d08cm" path="res://addons/keychain/assets/joy_axis.svg" id="4"] +[ext_resource type="Texture2D" uid="uid://bma7xj2rqqcr8" path="res://addons/keychain/assets/mouse.svg" id="5"] +[ext_resource type="PackedScene" uid="uid://bfjcafe2kvx7n" path="res://addons/keychain/ShortcutSelectorDialog.tscn" id="6"] + +[node name="ShortcutEdit" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_vertical = 3 +script = ExtResource("1") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 + +[node name="ProfileLabel" type="Label" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Shortcut profile:" + +[node name="ProfileOptionButton" type="OptionButton" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +mouse_default_cursor_shape = 2 + +[node name="NewProfile" type="Button" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +mouse_default_cursor_shape = 2 +text = "New" + +[node name="RenameProfile" type="Button" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +mouse_default_cursor_shape = 2 +text = "Rename" + +[node name="DeleteProfile" type="Button" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +mouse_default_cursor_shape = 2 +text = "Delete" + +[node name="OpenProfileFolder" type="Button" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +mouse_default_cursor_shape = 2 +text = "Open Folder" + +[node name="ShortcutTree" type="Tree" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 +hide_root = true + +[node name="ShortcutTypeMenu" type="PopupMenu" parent="."] +size = Vector2i(154, 116) +item_count = 4 +item_0/text = "Key" +item_0/icon = ExtResource("3") +item_0/id = 0 +item_1/text = "Mouse Button" +item_1/icon = ExtResource("5") +item_1/id = 1 +item_2/text = "Joy Button" +item_2/icon = ExtResource("2") +item_2/id = 2 +item_3/text = "Joy Axis" +item_3/icon = ExtResource("4") +item_3/id = 3 + +[node name="KeyboardShortcutSelectorDialog" parent="." instance=ExtResource("6")] +size = Vector2i(417, 134) + +[node name="MouseShortcutSelectorDialog" parent="." instance=ExtResource("6")] +size = Vector2i(417, 134) +input_type = 1 + +[node name="JoyKeyShortcutSelectorDialog" parent="." instance=ExtResource("6")] +size = Vector2i(417, 134) +input_type = 2 + +[node name="JoyAxisShortcutSelectorDialog" parent="." instance=ExtResource("6")] +size = Vector2i(417, 134) +input_type = 3 + +[node name="ProfileSettings" type="ConfirmationDialog" parent="."] + +[node name="ProfileName" type="LineEdit" parent="ProfileSettings"] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 192.0 +offset_bottom = 51.0 +caret_blink = true +caret_blink_interval = 0.5 + +[node name="DeleteConfirmation" type="ConfirmationDialog" parent="."] +size = Vector2i(427, 100) +dialog_text = "Are you sure you want to delete this shortcut profile?" + +[connection signal="item_selected" from="VBoxContainer/HBoxContainer/ProfileOptionButton" to="." method="_on_ProfileOptionButton_item_selected"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer/NewProfile" to="." method="_on_NewProfile_pressed"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer/RenameProfile" to="." method="_on_RenameProfile_pressed"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer/DeleteProfile" to="." method="_on_DeleteProfile_pressed"] +[connection signal="pressed" from="VBoxContainer/HBoxContainer/OpenProfileFolder" to="." method="_on_OpenProfileFolder_pressed"] +[connection signal="button_clicked" from="VBoxContainer/ShortcutTree" to="." method="_on_shortcut_tree_button_clicked"] +[connection signal="item_activated" from="VBoxContainer/ShortcutTree" to="." method="_on_ShortcutTree_item_activated"] +[connection signal="id_pressed" from="ShortcutTypeMenu" to="." method="_on_ShortcutTypeMenu_id_pressed"] +[connection signal="confirmed" from="ProfileSettings" to="." method="_on_ProfileSettings_confirmed"] +[connection signal="confirmed" from="DeleteConfirmation" to="." method="_on_DeleteConfirmation_confirmed"] diff --git a/game/addons/keychain/ShortcutProfile.gd b/game/addons/keychain/ShortcutProfile.gd new file mode 100644 index 0000000..0fb269a --- /dev/null +++ b/game/addons/keychain/ShortcutProfile.gd @@ -0,0 +1,38 @@ +class_name ShortcutProfile +extends Resource + +@export var name := "" +@export var customizable := true +@export var bindings := {} + + +func _init() -> void: + bindings = bindings.duplicate(true) + + +func fill_bindings() -> void: + var unnecessary_actions = bindings.duplicate() # Checks if the profile has any unused actions + for action in InputMap.get_actions(): + if not action in bindings: + bindings[action] = InputMap.action_get_events(action) + unnecessary_actions.erase(action) + for action in unnecessary_actions: + bindings.erase(action) + save() + + +func change_action(action: String) -> void: + if not customizable: + return + bindings[action] = InputMap.action_get_events(action) + save() + + +func save() -> bool: + if !customizable: + return false + var err := ResourceSaver.save(self, resource_path) + if err != OK: + print("Error saving shortcut profile %s. Error code: %s" % [resource_path, err]) + return false + return true diff --git a/game/addons/keychain/ShortcutSelectorDialog.gd b/game/addons/keychain/ShortcutSelectorDialog.gd new file mode 100644 index 0000000..38cb555 --- /dev/null +++ b/game/addons/keychain/ShortcutSelectorDialog.gd @@ -0,0 +1,199 @@ +extends ConfirmationDialog + +enum InputTypes { KEYBOARD, MOUSE, JOY_BUTTON, JOY_AXIS } + +@export var input_type: InputTypes = InputTypes.KEYBOARD +var listened_input: InputEvent + +@onready var root: Node = get_parent() +@onready var input_type_l: Label = $VBoxContainer/InputTypeLabel +@onready var entered_shortcut: LineEdit = $VBoxContainer/EnteredShortcut +@onready var option_button: OptionButton = $VBoxContainer/OptionButton +@onready var already_exists: Label = $VBoxContainer/AlreadyExistsLabel + + +func _ready() -> void: + set_process_input(false) + if input_type == InputTypes.KEYBOARD: + get_ok_button().focus_neighbor_top = entered_shortcut.get_path() + get_cancel_button().focus_neighbor_top = entered_shortcut.get_path() + entered_shortcut.focus_neighbor_bottom = get_ok_button().get_path() + else: + get_ok_button().focus_neighbor_top = option_button.get_path() + get_cancel_button().focus_neighbor_top = option_button.get_path() + option_button.focus_neighbor_bottom = get_ok_button().get_path() + +# get_close_button().focus_mode = Control.FOCUS_NONE + + +func _input(event: InputEvent) -> void: + if not event is InputEventKey: + return + if event.pressed: + listened_input = event + entered_shortcut.text = OS.get_keycode_string(event.get_keycode_with_modifiers()) + _show_assigned_state(event) + + +func _show_assigned_state(event: InputEvent) -> void: + var metadata = root.currently_editing_tree_item.get_metadata(0) + var action := "" + if metadata is InputEvent: # Editing an input event + action = root.currently_editing_tree_item.get_parent().get_metadata(0) + elif metadata is StringName: # Adding a new input event to an action + action = metadata + + var matching_pair: Array = _find_matching_event_in_map(action, event) + if matching_pair: + already_exists.text = tr("Already assigned to: %s") % root.get_action_name(matching_pair[0]) + else: + already_exists.text = "" + + +func _on_ShortcutSelectorDialog_confirmed() -> void: + if listened_input == null: + return + _apply_shortcut_change(listened_input) + + +func _apply_shortcut_change(input_event: InputEvent) -> void: + var metadata = root.currently_editing_tree_item.get_metadata(0) + if metadata is InputEvent: # Editing an input event + var parent_metadata = root.currently_editing_tree_item.get_parent().get_metadata(0) + var changed: bool = _set_shortcut(parent_metadata, metadata, input_event) + if !changed: + return + root.currently_editing_tree_item.set_metadata(0, input_event) + root.currently_editing_tree_item.set_text(0, root.event_to_str(input_event)) + elif metadata is StringName: # Adding a new input event to an action + var changed: bool = _set_shortcut(metadata, null, input_event) + if !changed: + return + root.add_event_tree_item(input_event, root.currently_editing_tree_item) + + +func _set_shortcut(action: StringName, old_event: InputEvent, new_event: InputEvent) -> bool: + if InputMap.action_has_event(action, new_event): # If the current action already has that event + return false + if old_event: + Keychain.action_erase_event(action, old_event) + + # Loop through other actions to see if the event exists there, to re-assign it + var matching_pair := _find_matching_event_in_map(action, new_event) + + if matching_pair: + var group := "" + if action in Keychain.actions: + group = Keychain.actions[action].group + + var action_to_replace: StringName = matching_pair[0] + var input_to_replace: InputEvent = matching_pair[1] + Keychain.action_erase_event(action_to_replace, input_to_replace) + Keychain.selected_profile.change_action(action_to_replace) + var tree_item: TreeItem = root.tree.get_root() + var prev_tree_item: TreeItem + while tree_item != null: # Loop through Tree's TreeItems... + var metadata = tree_item.get_metadata(0) + if metadata is InputEvent: + if input_to_replace.is_match(metadata): + var map_action: StringName = tree_item.get_parent().get_metadata(0) + if map_action == action_to_replace: + tree_item.free() + break + + tree_item = _get_next_tree_item(tree_item) + + Keychain.action_add_event(action, new_event) + Keychain.selected_profile.change_action(action) + return true + + +# Based on https://github.com/godotengine/godot/blob/master/scene/gui/tree.cpp#L685 +func _get_next_tree_item(current: TreeItem) -> TreeItem: + if current.get_first_child(): + current = current.get_first_child() + elif current.get_next(): + current = current.get_next() + else: + while current and !current.get_next(): + current = current.get_parent() + if current: + current = current.get_next() + return current + + +func _find_matching_event_in_map(action: StringName, event: InputEvent) -> Array: + var group := "" + if action in Keychain.actions: + group = Keychain.actions[action].group + + for map_action in InputMap.get_actions(): + if map_action in Keychain.ignore_actions: + continue + if Keychain.ignore_ui_actions and (map_action as String).begins_with("ui_"): + continue + for map_event in InputMap.action_get_events(map_action): + if !event.is_match(map_event): + continue + + if map_action in Keychain.actions: + # If it's local, check if it's the same group, otherwise ignore + if !Keychain.actions[action].global or !Keychain.actions[map_action].global: + if Keychain.actions[map_action].group != group: + continue + + return [map_action, map_event] + + return [] + + +func _on_ShortcutSelectorDialog_about_to_show() -> void: + if input_type == InputTypes.KEYBOARD: + listened_input = null + already_exists.text = "" + entered_shortcut.text = "" + await get_tree().process_frame + entered_shortcut.grab_focus() + else: + var metadata = root.currently_editing_tree_item.get_metadata(0) + if metadata is InputEvent: # Editing an input event + var index := 0 + if metadata is InputEventMouseButton: + index = metadata.button_index - 1 + elif metadata is InputEventJoypadButton: + index = metadata.button_index + elif metadata is InputEventJoypadMotion: + index = metadata.axis * 2 + index += round(metadata.axis_value) / 2.0 + 0.5 + option_button.select(index) + _on_OptionButton_item_selected(index) + + elif metadata is StringName: # Adding a new input event to an action + option_button.select(0) + _on_OptionButton_item_selected(0) + + +func _on_ShortcutSelectorDialog_popup_hide() -> void: + set_process_input(false) + + +func _on_OptionButton_item_selected(index: int) -> void: + if input_type == InputTypes.MOUSE: + listened_input = InputEventMouseButton.new() + listened_input.button_index = index + 1 + elif input_type == InputTypes.JOY_BUTTON: + listened_input = InputEventJoypadButton.new() + listened_input.button_index = index + elif input_type == InputTypes.JOY_AXIS: + listened_input = InputEventJoypadMotion.new() + listened_input.axis = index / 2 + listened_input.axis_value = -1.0 if index % 2 == 0 else 1.0 + _show_assigned_state(listened_input) + + +func _on_EnteredShortcut_focus_entered() -> void: + set_process_input(true) + + +func _on_EnteredShortcut_focus_exited() -> void: + set_process_input(false) diff --git a/game/addons/keychain/ShortcutSelectorDialog.tscn b/game/addons/keychain/ShortcutSelectorDialog.tscn new file mode 100644 index 0000000..d8f528f --- /dev/null +++ b/game/addons/keychain/ShortcutSelectorDialog.tscn @@ -0,0 +1,46 @@ +[gd_scene load_steps=2 format=3 uid="uid://bfjcafe2kvx7n"] + +[ext_resource type="Script" path="res://addons/keychain/ShortcutSelectorDialog.gd" id="1"] + +[node name="ShortcutSelectorDialog" type="ConfirmationDialog"] +script = ExtResource("1") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 341.0 +offset_bottom = 64.0 + +[node name="InputTypeLabel" type="Label" parent="VBoxContainer"] +layout_mode = 2 +offset_right = 333.0 +offset_bottom = 14.0 +text = "Press a key or a key combination to set the shortcut" + +[node name="EnteredShortcut" type="LineEdit" parent="VBoxContainer"] +visible = false +layout_mode = 2 +offset_top = 18.0 +offset_right = 333.0 +offset_bottom = 32.0 +editable = false +virtual_keyboard_enabled = false + +[node name="OptionButton" type="OptionButton" parent="VBoxContainer"] +layout_mode = 2 +offset_top = 18.0 +offset_right = 333.0 +offset_bottom = 38.0 +mouse_default_cursor_shape = 2 + +[node name="AlreadyExistsLabel" type="Label" parent="VBoxContainer"] +layout_mode = 2 +offset_top = 42.0 +offset_right = 333.0 +offset_bottom = 56.0 + +[connection signal="about_to_popup" from="." to="." method="_on_ShortcutSelectorDialog_about_to_show"] +[connection signal="confirmed" from="." to="." method="_on_ShortcutSelectorDialog_confirmed"] +[connection signal="focus_entered" from="VBoxContainer/EnteredShortcut" to="." method="_on_EnteredShortcut_focus_entered"] +[connection signal="focus_exited" from="VBoxContainer/EnteredShortcut" to="." method="_on_EnteredShortcut_focus_exited"] +[connection signal="item_selected" from="VBoxContainer/OptionButton" to="." method="_on_OptionButton_item_selected"] diff --git a/game/addons/keychain/assets/add.svg b/game/addons/keychain/assets/add.svg new file mode 100644 index 0000000..afad08a --- /dev/null +++ b/game/addons/keychain/assets/add.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1v6h-6v2h6v6h2v-6h6v-2h-6v-6z" fill="#e0e0e0"/></svg> diff --git a/game/addons/keychain/assets/add.svg.import b/game/addons/keychain/assets/add.svg.import new file mode 100644 index 0000000..25442bd --- /dev/null +++ b/game/addons/keychain/assets/add.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4433pa25cxp2" +path="res://.godot/imported/add.svg-4084e314648c872072757f9b0f544cf9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/keychain/assets/add.svg" +dest_files=["res://.godot/imported/add.svg-4084e314648c872072757f9b0f544cf9.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/game/addons/keychain/assets/close.svg b/game/addons/keychain/assets/close.svg new file mode 100644 index 0000000..331727a --- /dev/null +++ b/game/addons/keychain/assets/close.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3.7578 2.3438-1.4141 1.4141 4.2422 4.2422-4.2422 4.2422 1.4141 1.4141 4.2422-4.2422 4.2422 4.2422 1.4141-1.4141-4.2422-4.2422 4.2422-4.2422-1.4141-1.4141-4.2422 4.2422z" fill="#e0e0e0"/></svg> diff --git a/game/addons/keychain/assets/close.svg.import b/game/addons/keychain/assets/close.svg.import new file mode 100644 index 0000000..d1bc742 --- /dev/null +++ b/game/addons/keychain/assets/close.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://f1gcylay6c6f" +path="res://.godot/imported/close.svg-12d57fa7e5a34826f312eed6ba1feb1a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/keychain/assets/close.svg" +dest_files=["res://.godot/imported/close.svg-12d57fa7e5a34826f312eed6ba1feb1a.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/game/addons/keychain/assets/edit.svg b/game/addons/keychain/assets/edit.svg new file mode 100644 index 0000000..6fc7ae0 --- /dev/null +++ b/game/addons/keychain/assets/edit.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1c-.554 0-1 .446-1 1v2h4v-2c0-.554-.446-1-1-1zm-1 4v7l2 3 2-3v-7zm1 1h1v5h-1z" fill="#e0e0e0"/></svg> diff --git a/game/addons/keychain/assets/edit.svg.import b/game/addons/keychain/assets/edit.svg.import new file mode 100644 index 0000000..73ee367 --- /dev/null +++ b/game/addons/keychain/assets/edit.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://df8gl3u2nqpl3" +path="res://.godot/imported/edit.svg-cd9834545a8696f1e8611efa12a48f33.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/keychain/assets/edit.svg" +dest_files=["res://.godot/imported/edit.svg-cd9834545a8696f1e8611efa12a48f33.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/game/addons/keychain/assets/folder.svg b/game/addons/keychain/assets/folder.svg new file mode 100644 index 0000000..c2def25 --- /dev/null +++ b/game/addons/keychain/assets/folder.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2 2a1 1 0 0 0 -1 1v2 6 2a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-7a1 1 0 0 0 -1-1h-4a1 1 0 0 1 -1-1v-1a1 1 0 0 0 -1-1z" fill="#e0e0e0"/></svg> diff --git a/game/addons/keychain/assets/folder.svg.import b/game/addons/keychain/assets/folder.svg.import new file mode 100644 index 0000000..d2eece2 --- /dev/null +++ b/game/addons/keychain/assets/folder.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0gbmkb8xwksb" +path="res://.godot/imported/folder.svg-490bb7e2d2aa4425de998b1d382cf534.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/keychain/assets/folder.svg" +dest_files=["res://.godot/imported/folder.svg-490bb7e2d2aa4425de998b1d382cf534.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/game/addons/keychain/assets/joy_axis.svg b/game/addons/keychain/assets/joy_axis.svg new file mode 100644 index 0000000..1ab65f0 --- /dev/null +++ b/game/addons/keychain/assets/joy_axis.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -1036.4)"><path d="m27 1038.4h7v14h-7z" fill="#fff" fill-opacity=".99608"/><g fill="#e0e0e0"><path d="m3 1a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h12v-14zm4 2h2a1 1 0 0 1 1 1v2h2a1 1 0 0 1 1 1v2a1 1 0 0 1 -1 1h-2v2a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1-1v-2h-2a1 1 0 0 1 -1-1v-2a1 1 0 0 1 1-1h2v-2a1 1 0 0 1 1-1z" fill-opacity=".99608" transform="translate(0 1036.4)"/><circle cx="8" cy="1044.4" r="1"/></g></g></svg> diff --git a/game/addons/keychain/assets/joy_axis.svg.import b/game/addons/keychain/assets/joy_axis.svg.import new file mode 100644 index 0000000..f517dae --- /dev/null +++ b/game/addons/keychain/assets/joy_axis.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bb6q6om3d08cm" +path="res://.godot/imported/joy_axis.svg-9e1156700cfe46afb1ca622536a9a2e1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/keychain/assets/joy_axis.svg" +dest_files=["res://.godot/imported/joy_axis.svg-9e1156700cfe46afb1ca622536a9a2e1.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/game/addons/keychain/assets/joy_button.svg b/game/addons/keychain/assets/joy_button.svg new file mode 100644 index 0000000..080d91a --- /dev/null +++ b/game/addons/keychain/assets/joy_button.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".99608" transform="translate(0 -1036.4)"><path d="m27 1038.4h7v14h-7z" fill="#fff"/><path d="m1 1v14h12c1.1046 0 2-.8954 2-2v-10c0-1.1046-.89543-2-2-2zm7 1a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2zm-4 4a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2zm8 0a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2zm-4 4a2 2 0 0 1 2 2 2 2 0 0 1 -2 2 2 2 0 0 1 -2-2 2 2 0 0 1 2-2z" fill="#e0e0e0" transform="translate(0 1036.4)"/></g></svg> diff --git a/game/addons/keychain/assets/joy_button.svg.import b/game/addons/keychain/assets/joy_button.svg.import new file mode 100644 index 0000000..8963b8b --- /dev/null +++ b/game/addons/keychain/assets/joy_button.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ca58ufal2ufd8" +path="res://.godot/imported/joy_button.svg-df5663c6f296cab556e81b9c770699d0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/keychain/assets/joy_button.svg" +dest_files=["res://.godot/imported/joy_button.svg-df5663c6f296cab556e81b9c770699d0.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/game/addons/keychain/assets/keyboard.svg b/game/addons/keychain/assets/keyboard.svg new file mode 100644 index 0000000..b9dfab7 --- /dev/null +++ b/game/addons/keychain/assets/keyboard.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".996"><path d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm1.543 1.139h1.393l1.834 4.199h1.295v.437c.708.052 1.246.239 1.61.559.368.316.55.747.55 1.295 0 .552-.182.99-.55 1.314-.368.32-.906.505-1.61.553v.467h-1.294v-.473c-.708-.06-1.247-.248-1.615-.564-.364-.316-.545-.75-.545-1.297 0-.548.181-.977.545-1.29.368-.315.907-.504 1.615-.564v-.437h-1.464l-.282-.733h-1.595l-.284.733h-1.439l1.836-4.2zm.684 1.39-.409 1.057h.817zm3.84 4.338v1.526c.28-.04.483-.12.607-.24.124-.125.185-.302.185-.53 0-.224-.063-.396-.191-.516-.124-.12-.326-.2-.602-.24zm-1.296.006c-.284.04-.487.12-.61.24-.12.116-.182.288-.182.516 0 .22.065.392.193.512.132.12.331.202.6.246v-1.514z" fill="#e0e0e0"/><path d="m27 2h7v14h-7z" fill="#fff"/><path d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z" fill="#e0e0e0"/></g></svg> diff --git a/game/addons/keychain/assets/keyboard.svg.import b/game/addons/keychain/assets/keyboard.svg.import new file mode 100644 index 0000000..0294680 --- /dev/null +++ b/game/addons/keychain/assets/keyboard.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2s5rm4nec5yh" +path="res://.godot/imported/keyboard.svg-fac365b6f70899f1dfa71ce4b4761ac6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/keychain/assets/keyboard.svg" +dest_files=["res://.godot/imported/keyboard.svg-fac365b6f70899f1dfa71ce4b4761ac6.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/game/addons/keychain/assets/keyboard_physical.svg b/game/addons/keychain/assets/keyboard_physical.svg new file mode 100644 index 0000000..4364e0b --- /dev/null +++ b/game/addons/keychain/assets/keyboard_physical.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill-opacity=".996"><path d="m4 2a1 1 0 0 0 -1 1v9.084c0 .506.448.916 1 .916h8c.552 0 1-.41 1-.916v-9.084a1 1 0 0 0 -1-1zm2.762 1.768h2.476l3.264 7.464h-2.604l-.502-1.3h-2.835l-.502 1.3h-2.561zm1.217 2.474-.725 1.878h1.45z" fill="#e0e0e0"/><path d="m27 2h7v14h-7z" fill="#fff"/><path d="m1 4v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a1 1 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9z" fill="#e0e0e0"/></g></svg> diff --git a/game/addons/keychain/assets/keyboard_physical.svg.import b/game/addons/keychain/assets/keyboard_physical.svg.import new file mode 100644 index 0000000..86e2b3e --- /dev/null +++ b/game/addons/keychain/assets/keyboard_physical.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmh8eaibhn5y8" +path="res://.godot/imported/keyboard_physical.svg-f50c796569ade32b57ece1ba0bd7dfbb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/keychain/assets/keyboard_physical.svg" +dest_files=["res://.godot/imported/keyboard_physical.svg-f50c796569ade32b57ece1ba0bd7dfbb.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/game/addons/keychain/assets/mouse.svg b/game/addons/keychain/assets/mouse.svg new file mode 100644 index 0000000..2175120 --- /dev/null +++ b/game/addons/keychain/assets/mouse.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m7 1.1016a5 5 0 0 0 -4 4.8984h4zm2 .0039063v4.8945h4a5 5 0 0 0 -4-4.8945zm-6 6.8945v2a5 5 0 0 0 5 5 5 5 0 0 0 5-5v-2z" fill="#e0e0e0"/></svg> diff --git a/game/addons/keychain/assets/mouse.svg.import b/game/addons/keychain/assets/mouse.svg.import new file mode 100644 index 0000000..21c2216 --- /dev/null +++ b/game/addons/keychain/assets/mouse.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bma7xj2rqqcr8" +path="res://.godot/imported/mouse.svg-559695787f3bb55c16dc66bd6a9b9032.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/keychain/assets/mouse.svg" +dest_files=["res://.godot/imported/mouse.svg-559695787f3bb55c16dc66bd6a9b9032.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/game/addons/keychain/assets/shortcut.svg b/game/addons/keychain/assets/shortcut.svg new file mode 100644 index 0000000..4ef16f0 --- /dev/null +++ b/game/addons/keychain/assets/shortcut.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m4 2c-.55228 0-1 .4477-1 1v9.084c.0004015.506.448.91602 1 .91602h8c.552 0 .9996-.41002 1-.91602v-9.084c0-.5523-.44772-1-1-1zm-3 2v9a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-9h-1v9a.99998.99998 0 0 1 -1 1h-10a1 1 0 0 1 -1-1v-9zm6 0h3l-1 3h2l-4 4 1-3h-2z" fill="#e0e0e0" fill-opacity=".99608"/></svg> diff --git a/game/addons/keychain/assets/shortcut.svg.import b/game/addons/keychain/assets/shortcut.svg.import new file mode 100644 index 0000000..ae3bd49 --- /dev/null +++ b/game/addons/keychain/assets/shortcut.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bmi24jp4fqi7k" +path="res://.godot/imported/shortcut.svg-401daac18a817142e2b29e5801e00053.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/keychain/assets/shortcut.svg" +dest_files=["res://.godot/imported/shortcut.svg-401daac18a817142e2b29e5801e00053.ctex"] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/bptc_ldr=0 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/game/addons/keychain/plugin.cfg b/game/addons/keychain/plugin.cfg new file mode 100644 index 0000000..095c8ee --- /dev/null +++ b/game/addons/keychain/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Keychain" +description="A plugin for the Godot Engine that aims to give the player full control over the input actions of the game." +author="Orama Interactive" +version="2.0" +script="plugin.gd" diff --git a/game/addons/keychain/plugin.gd b/game/addons/keychain/plugin.gd new file mode 100644 index 0000000..1eb1d3c --- /dev/null +++ b/game/addons/keychain/plugin.gd @@ -0,0 +1,10 @@ +@tool +extends EditorPlugin + + +func _enter_tree() -> void: + add_autoload_singleton("Keychain", "res://addons/keychain/Keychain.gd") + + +func _exit_tree() -> void: + remove_autoload_singleton("Keychain") diff --git a/game/addons/keychain/profiles/default.tres b/game/addons/keychain/profiles/default.tres new file mode 100644 index 0000000..b952157 --- /dev/null +++ b/game/addons/keychain/profiles/default.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="ShortcutProfile" load_steps=2 format=3 uid="uid://df04uev1epmmo"] + +[ext_resource type="Script" path="res://addons/keychain/ShortcutProfile.gd" id="1"] + +[resource] +script = ExtResource("1") +name = "Default" +customizable = false +bindings = {} diff --git a/game/addons/keychain/translations/README.md b/game/addons/keychain/translations/README.md new file mode 100644 index 0000000..086527b --- /dev/null +++ b/game/addons/keychain/translations/README.md @@ -0,0 +1,4 @@ +# Localization files for Keychain +Keychain uses .po files to handle localization. More information in the Godot documentation: [Localization using gettext](https://docs.godotengine.org/en/stable/tutorials/i18n/localization_using_gettext.html) + +Simply add a .po file of the language you want to provide localization for, and Keychain will automatically add it to the TranslationServer when the project runs. diff --git a/game/addons/keychain/translations/Translations.pot b/game/addons/keychain/translations/Translations.pot new file mode 100644 index 0000000..1f10c77 --- /dev/null +++ b/game/addons/keychain/translations/Translations.pot @@ -0,0 +1,209 @@ +msgid "" +msgstr "" + +msgid "OK" +msgstr "" + +msgid "Cancel" +msgstr "" + +msgid "Add" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Delete" +msgstr "" + +msgid "Shortcut profile:" +msgstr "" + +msgid "Default" +msgstr "" + +msgid "Custom" +msgstr "" + +msgid "New" +msgstr "" + +msgid "Rename" +msgstr "" + +msgid "Open Folder" +msgstr "" + +msgid "New Shortcut Profile" +msgstr "" + +msgid "Rename Shortcut Profile" +msgstr "" + +msgid "Are you sure you want to delete this shortcut profile?" +msgstr "" + +msgid "Key" +msgstr "" + +msgid "Mouse Button" +msgstr "" + +msgid "Joy Button" +msgstr "" + +msgid "Joy Axis" +msgstr "" + +msgid "Button" +msgstr "" + +msgid "Axis" +msgstr "" + +msgid "(Physical)" +msgstr "" + +msgid "Set the shortcut" +msgstr "" + +msgid "Press a key or a key combination to set the shortcut" +msgstr "" + +msgid "Mouse Button Index:" +msgstr "" + +msgid "Joypad Button Index:" +msgstr "" + +msgid "Joypad Axis Index:" +msgstr "" + +msgid "Already assigned to: %s" +msgstr "" + +msgid "Left Button" +msgstr "" + +msgid "Right Button" +msgstr "" + +msgid "Middle Button" +msgstr "" + +msgid "Wheel Up Button" +msgstr "" + +msgid "Wheel Down Button" +msgstr "" + +msgid "Wheel Left Button" +msgstr "" + +msgid "Wheel Right Button" +msgstr "" + +msgid "X Button 1" +msgstr "" + +msgid "X Button 2" +msgstr "" + +msgid "DualShock Cross, Xbox A, Nintendo B" +msgstr "" + +msgid "DualShock Circle, Xbox B, Nintendo A" +msgstr "" + +msgid "DualShock Square, Xbox X, Nintendo Y" +msgstr "" + +msgid "DualShock Triangle, Xbox Y, Nintendo X" +msgstr "" + +msgid "L, L1" +msgstr "" + +msgid "R, R1" +msgstr "" + +msgid "L2" +msgstr "" + +msgid "R2" +msgstr "" + +msgid "L3" +msgstr "" + +msgid "R3" +msgstr "" + +msgid "Select, DualShock Share, Nintendo -" +msgstr "" + +msgid "Start, DualShock Options, Nintendo +" +msgstr "" + +msgid "D-Pad Up" +msgstr "" + +msgid "D-Pad Down" +msgstr "" + +msgid "D-Pad Left" +msgstr "" + +msgid "D-Pad Right" +msgstr "" + +msgid "Home, DualShock PS, Guide" +msgstr "" + +msgid "Xbox Share, PS5 Microphone, Nintendo Capture" +msgstr "" + +msgid "Xbox Paddle 1" +msgstr "" + +msgid "Xbox Paddle 2" +msgstr "" + +msgid "Xbox Paddle 3" +msgstr "" + +msgid "Xbox Paddle 4" +msgstr "" + +msgid "PS4/5 Touchpad" +msgstr "" + +msgid "(Left Stick Left)" +msgstr "" + +msgid "(Left Stick Right)" +msgstr "" + +msgid "(Left Stick Up)" +msgstr "" + +msgid "(Left Stick Down)" +msgstr "" + +msgid "(Right Stick Left)" +msgstr "" + +msgid "(Right Stick Right)" +msgstr "" + +msgid "(Right Stick Up)" +msgstr "" + +msgid "(Right Stick Down)" +msgstr "" + +msgid "(L2)" +msgstr "" + +msgid "(R2)" +msgstr "" diff --git a/game/addons/keychain/translations/el_GR.po b/game/addons/keychain/translations/el_GR.po new file mode 100644 index 0000000..4b6ce8b --- /dev/null +++ b/game/addons/keychain/translations/el_GR.po @@ -0,0 +1,215 @@ +msgid "" +msgstr "" +"Project-Id-Version: Keychain\n" +"Last-Translator: Overloaded\n" +"Language-Team: none\n" +"Language: el_GR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +msgid "OK" +msgstr "Εντάξει" + +msgid "Cancel" +msgstr "Άκυρο" + +msgid "Add" +msgstr "Προσθήκη" + +msgid "Edit" +msgstr "Επεξεργασία" + +msgid "Delete" +msgstr "Διαγραφή" + +msgid "Shortcut profile:" +msgstr "Προφίλ συντομέυσεων:" + +msgid "Default" +msgstr "Προεπιλογή" + +msgid "Custom" +msgstr "Προσαρμοσμένο" + +msgid "New" +msgstr "Νέο" + +msgid "Rename" +msgstr "Μετονομασία" + +msgid "Open Folder" +msgstr "Άνοιγμα φακέλου" + +msgid "New Shortcut Profile" +msgstr "Νέο προφίλ συντομέυσεων" + +msgid "Rename Shortcut Profile" +msgstr "Μετονομασία προφίλ συντομεύσεων" + +msgid "Are you sure you want to delete this shortcut profile?" +msgstr "Είστε σίγουροι πως θέλετε να διαγραφτεί αυτό το προφίλ συντομέυσεων;" + +msgid "Key" +msgstr "Πλήκτρο" + +msgid "Mouse Button" +msgstr "Κουμπί ποντικιού" + +msgid "Joy Button" +msgstr "Κουμπί χειριστηρίου" + +msgid "Joy Axis" +msgstr "Άξονας χειριστηρίου" + +msgid "Button" +msgstr "Κουμπί" + +msgid "Axis" +msgstr "Άξονας" + +msgid "(Physical)" +msgstr "(Φυσικό)" + +msgid "Set the shortcut" +msgstr "Επιλέξτε μια συντόμευση" + +msgid "Press a key or a key combination to set the shortcut" +msgstr "Πατήστε ένα πλήκτρο ή συνδυασμό πλήκτρων για να ορίσετε τη συντόμευση" + +msgid "Mouse Button Index:" +msgstr "Δείκτης κουμπιού ποντικού" + +msgid "Joypad Button Index:" +msgstr "Δείκτης κουμπιού χειριστηρίου" + +msgid "Joypad Axis Index:" +msgstr "Δείκτης άξονα χειριστηρίου" + +msgid "Already assigned to: %s" +msgstr "Έχει ήδη εκχωρηθεί σε: %s" + +msgid "Left Button" +msgstr "Αριστερό κουμπί" + +msgid "Right Button" +msgstr "Δεξί κουμπί" + +msgid "Middle Button" +msgstr "Μεσαίο κουμπί" + +msgid "Wheel Up Button" +msgstr "Τρόχος πάνω" + +msgid "Wheel Down Button" +msgstr "Τρόχος κάτω" + +msgid "Wheel Left Button" +msgstr "Τρόχος αριστερά" + +msgid "Wheel Right Button" +msgstr "Τρόχος δεξιά" + +msgid "X Button 1" +msgstr "Κουμπί X 1" + +msgid "X Button 2" +msgstr "Κουμπί X 2" + +msgid "DualShock Cross, Xbox A, Nintendo B" +msgstr "DualShock Σταυρός, Xbox A, Nintendo B" + +msgid "DualShock Circle, Xbox B, Nintendo A" +msgstr "DualShock Κύκλος, Xbox B, Nintendo A" + +msgid "DualShock Square, Xbox X, Nintendo Y" +msgstr "DualShock Τετράγωνο, Xbox X, Nintendo Y" + +msgid "DualShock Triangle, Xbox Y, Nintendo X" +msgstr "DualShock Τρίγωνο, Xbox Y, Nintendo X" + +msgid "L, L1" +msgstr "L, L1" + +msgid "R, R1" +msgstr "R, R1" + +msgid "L2" +msgstr "L2" + +msgid "R2" +msgstr "R2" + +msgid "L3" +msgstr "L3" + +msgid "R3" +msgstr "R3" + +msgid "Select, DualShock Share, Nintendo -" +msgstr "" + +msgid "Start, DualShock Options, Nintendo +" +msgstr "" + +msgid "D-Pad Up" +msgstr "D-Pad Πάνω" + +msgid "D-Pad Down" +msgstr "D-Pad Κάτω" + +msgid "D-Pad Left" +msgstr "D-Pad Αριστερά" + +msgid "D-Pad Right" +msgstr "D-Pad Δεξιά" + +msgid "Home, DualShock PS, Guide" +msgstr "" + +msgid "Xbox Share, PS5 Microphone, Nintendo Capture" +msgstr "Xbox Share, PS5 Μικρόφωνο, Nintendo Capture" + +msgid "Xbox Paddle 1" +msgstr "" + +msgid "Xbox Paddle 2" +msgstr "" + +msgid "Xbox Paddle 3" +msgstr "" + +msgid "Xbox Paddle 4" +msgstr "" + +msgid "PS4/5 Touchpad" +msgstr "PS4/5 επιφάνεια αφής" + +msgid "(Left Stick Left)" +msgstr "(Αριστερό Stick Αριστερά)" + +msgid "(Left Stick Right)" +msgstr "(Αριστερό Stick Δεξιά)" + +msgid "(Left Stick Up)" +msgstr "(Αριστερό Stick Πάνω)" + +msgid "(Left Stick Down)" +msgstr "(Αριστερό Stick Κάτω)" + +msgid "(Right Stick Left)" +msgstr "(Δεξί Stick Αριστερά)" + +msgid "(Right Stick Right)" +msgstr "(Δεξί Stick Δεξιά)" + +msgid "(Right Stick Up)" +msgstr "(Δεξί Stick Πάνω)" + +msgid "(Right Stick Down)" +msgstr "(Δεξί Stick Κάτω)" + +msgid "(L2)" +msgstr "(L2)" + +msgid "(R2)" +msgstr "(R2)" diff --git a/game/project.godot b/game/project.godot index 34d4267..35b92f6 100644 --- a/game/project.godot +++ b/game/project.godot @@ -14,10 +14,13 @@ config/name="OpenVic2" run/main_scene="res://src/GameMenu.tscn" config/features=PackedStringArray("4.0", "Forward Plus") config/icon="res://icon.svg" +config/project_settings_override.template="user://settings.cfg" [autoload] +Events="*res://src/Autoload/Events.gd" Resolution="*res://src/Autoload/Resolution.gd" +Keychain="*res://addons/keychain/Keychain.gd" [display] @@ -28,6 +31,10 @@ window/size/resizable=false window/stretch/mode="canvas_items" window/stretch/aspect="ignore" +[editor_plugins] + +enabled=PackedStringArray("res://addons/keychain/plugin.cfg") + [internationalization] locale/translation_remaps={} @@ -41,6 +48,10 @@ locale/country_short_name={ limits/message_queue/max_size_kb=16384 +[openvic2] + +settings/settings_file_path="user://settings.cfg" + [rendering] textures/lossless_compression/force_png=true diff --git a/game/src/Autoload/Events.gd b/game/src/Autoload/Events.gd new file mode 100644 index 0000000..f0f60b7 --- /dev/null +++ b/game/src/Autoload/Events.gd @@ -0,0 +1,3 @@ +extends Node + +var Options = preload("Events/Options.gd").new() diff --git a/game/src/Autoload/Events/Options.gd b/game/src/Autoload/Events/Options.gd new file mode 100644 index 0000000..0acaa63 --- /dev/null +++ b/game/src/Autoload/Events/Options.gd @@ -0,0 +1,22 @@ +extends RefCounted + +signal save_settings(save_file: ConfigFile) +signal load_settings(load_file: ConfigFile) +signal reset_settings() + +func load_settings_from_file() -> void: + load_settings.emit(_settings_file) + +func save_settings_from_file() -> void: + save_settings.emit(_settings_file) + _settings_file.save(_settings_file_path) + +func try_reset_settings() -> void: + reset_settings.emit() + +var _settings_file_path := ProjectSettings.get_setting("openvic2/settings/settings_file_path", "user://settings.cfg") as String +var _settings_file := ConfigFile.new() + +func _init(): + if FileAccess.file_exists(_settings_file_path): + _settings_file.load(_settings_file_path) diff --git a/game/src/Autoload/Resolution.gd b/game/src/Autoload/Resolution.gd index cde46f5..ac54c0d 100644 --- a/game/src/Autoload/Resolution.gd +++ b/game/src/Autoload/Resolution.gd @@ -1,28 +1,38 @@ 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) -} +var _resolutions : Array[Dictionary]= [ + { "name": &"", "value": Vector2i(3840,2160) }, + { "name": &"", "value": Vector2i(2560,1080) }, + { "name": &"", "value": Vector2i(1920,1080) }, + { "name": &"", "value": Vector2i(1366,768) }, + { "name": &"", "value": Vector2i(1536,864) }, + { "name": &"", "value": Vector2i(1280,720) }, + { "name": &"", "value": Vector2i(1440,900) }, + { "name": &"", "value": Vector2i(1600,900) }, + { "name": &"", "value": Vector2i(1024,600) }, + { "name": &"", "value": Vector2i(800,600) } +] + +func _ready(): + for resolution in _resolutions: + resolution["tag"] = _get_name_of_resolution(resolution["name"], resolution["value"]) 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) + var resolution := _get_resolution_by_name(resolution_name) + if resolution.x < 0 and resolution.y < 0: + return default + return resolution -func get_resolution_name_list() -> Array: - return _resolutions.keys() +func get_resolution_name_list() -> Array[StringName]: + var result : Array[StringName] = [] + for resolution in _resolutions: + result.append(resolution["tag"]) + return result -func get_current_resolution() -> Vector2: +func get_current_resolution() -> Vector2i: var window := get_viewport().get_window() match window.mode: Window.MODE_EXCLUSIVE_FULLSCREEN, Window.MODE_FULLSCREEN: @@ -30,7 +40,7 @@ func get_current_resolution() -> Vector2: _: return window.size -func set_resolution(resolution : Vector2) -> void: +func set_resolution(resolution : Vector2i) -> void: var window := get_viewport().get_window() match window.mode: Window.MODE_EXCLUSIVE_FULLSCREEN, Window.MODE_FULLSCREEN: @@ -41,3 +51,14 @@ func set_resolution(resolution : Vector2) -> void: func reset_resolution() -> void: set_resolution(get_current_resolution()) + +func _get_name_of_resolution(resolution_name : StringName, resolution_value : Vector2i) -> StringName: + if resolution_name != null and not resolution_name.is_empty(): + return "%s (%sx%s)" % [resolution_name, resolution_value.x, resolution_value.y] + return "%sx%s" % [resolution_value.x, resolution_value.y] + +func _get_resolution_by_name(resolution_name : StringName) -> Vector2i: + for resolution in _resolutions: + if resolution["name"] == resolution_name or resolution["tag"] == resolution_name: + return resolution["value"] + return Vector2i(-1, -1) diff --git a/game/src/GameMenu.gd b/game/src/GameMenu.gd index 1f5a77e..c6174bb 100644 --- a/game/src/GameMenu.gd +++ b/game/src/GameMenu.gd @@ -1,5 +1,7 @@ extends Control +func _ready(): + Events.Options.load_settings_from_file() func _on_main_menu_options_button_pressed(): $OptionsMenu.toggle_locale_button_visibility(false) diff --git a/game/src/LocaleButton.gd b/game/src/LocaleButton.gd index eed815e..32807d0 100644 --- a/game/src/LocaleButton.gd +++ b/game/src/LocaleButton.gd @@ -4,8 +4,6 @@ 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()] @@ -20,7 +18,18 @@ func _ready(): add_item("%s, %s" % [locale_first_part, locale_second_part]) + Events.Options.load_settings.connect(load_setting) + Events.Options.save_settings.connect(save_setting) + + +func load_setting(file : ConfigFile): + var locale_index := _locales_list.find(file.get_value("Localization", "Locale", "") as String) + if locale_index != -1: + selected = locale_index + +func save_setting(file : ConfigFile): + file.set_value("Localization", "Locale", _locales_list[selected]) func _on_item_selected(index): - print("Selected locale " + _locales_list[index]) TranslationServer.set_locale(_locales_list[index]) + Events.Options.save_settings_from_file.call_deferred() diff --git a/game/src/OptionMenu/ControlsTab.tscn b/game/src/OptionMenu/ControlsTab.tscn new file mode 100644 index 0000000..b84dc85 --- /dev/null +++ b/game/src/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/OptionMenu/MonitorDisplaySelector.gd b/game/src/OptionMenu/MonitorDisplaySelector.gd index 600b296..f2f0dc8 100644 --- a/game/src/OptionMenu/MonitorDisplaySelector.gd +++ b/game/src/OptionMenu/MonitorDisplaySelector.gd @@ -1,16 +1,14 @@ extends SettingOptionButton - -func _ready(): +func _setup_button(): clear() for screen_index in range(DisplayServer.get_screen_count()): add_item("Monitor %d" % (screen_index + 1)) + default_selected = get_viewport().get_window().current_screen 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 index e3c8433..5aba7f2 100644 --- a/game/src/OptionMenu/OptionsMenu.gd +++ b/game/src/OptionMenu/OptionsMenu.gd @@ -1,27 +1,9 @@ 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 @@ -33,7 +15,7 @@ func _ready(): var reset_button := Button.new() reset_button.text = "R" - reset_button.pressed.connect(func(): reset_settings.emit()) + reset_button.pressed.connect(Events.Options.try_reset_settings) button_list.add_child(reset_button) var back_button := Button.new() @@ -42,6 +24,8 @@ func _ready(): 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: @@ -51,22 +35,24 @@ func _notification(what): # 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) + Events.Options.save_settings_from_file() 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() + Events.Options.save_settings_from_file() + +func _save_overrides() -> void: + var override_path := ProjectSettings.get_setting("application/config/project_settings_override") as String + if override_path == null or override_path.is_empty(): + override_path = ProjectSettings.get_setting("openvic2/settings/settings_file_path") as String + var file := ConfigFile.new() + file.load(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) + file.save(override_path) diff --git a/game/src/OptionMenu/OptionsMenu.tscn b/game/src/OptionMenu/OptionsMenu.tscn index 0ed531f..eafe37f 100644 --- a/game/src/OptionMenu/OptionsMenu.tscn +++ b/game/src/OptionMenu/OptionsMenu.tscn @@ -2,10 +2,10 @@ [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"] +[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://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="Control"] layout_mode = 3 @@ -34,114 +34,18 @@ size_flags_vertical = 3 tab_alignment = 1 use_hidden_tabs_for_min_size = true -[node name="Video" type="HBoxContainer" parent="Margin/Tab"] +[node name="Video" parent="Margin/Tab" instance=ExtResource("2_ji8xr")] 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"] +[node name="Sound" parent="Margin/Tab" instance=ExtResource("3_4w35t")] 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"] +[node name="Controls" parent="Margin/Tab" instance=ExtResource("4_vdhjp")] 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"] +[node name="Other" parent="Margin/Tab" instance=ExtResource("5_ahefp")] layout_mode = 2 [node name="LocaleVBox" type="VBoxContainer" parent="."] @@ -161,21 +65,3 @@ 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/OtherTab.tscn b/game/src/OptionMenu/OtherTab.tscn new file mode 100644 index 0000000..0ffc92d --- /dev/null +++ b/game/src/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/OptionMenu/ResolutionSelector.gd b/game/src/OptionMenu/ResolutionSelector.gd index ef1a0ff..e602bab 100644 --- a/game/src/OptionMenu/ResolutionSelector.gd +++ b/game/src/OptionMenu/ResolutionSelector.gd @@ -1,24 +1,58 @@ extends SettingOptionButton -func _ready(): - print("Resolution selector ready") +@export +var default_value : Vector2i = Vector2i(-1, -1) + +func add_resolution(value : Vector2i, selection_name : String = "") -> void: + if selection_name.is_empty(): + selection_name = "%sx%s" % [value.x, value.y] + add_item(selection_name) + set_item_metadata(item_count - 1, value) + +func find_resolution_value(value : Vector2i) -> int: + for item_index in range(item_count): + if get_item_metadata(item_index) == value: + return item_index + return -1 + +func _setup_button(): + 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") clear() - var resolution_index := 0 + default_selected = -1 + selected = -1 for resolution in Resolution.get_resolution_name_list(): - add_item(resolution) + var resolution_value := Resolution.get_resolution(resolution) + add_resolution(resolution_value, 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) + if resolution_value == default_value: + default_selected = item_count - 1 - resolution_index += 1 + if resolution_value == Resolution.get_current_resolution(): + selected = item_count - 1 + if default_selected == -1: + add_resolution(default_value) + default_selected = item_count - 1 -func _on_item_selected(index): - print("Selected index: %d" % index) + if selected == -1: + selected = default_selected + +func _get_value_for_file(select_value : int): + return get_item_metadata(select_value) - var resolution_size : Vector2i = Resolution.get_resolution(get_item_text(index)) - Resolution.set_resolution(resolution_size) +func _set_value_from_file(load_value): + var resolution_value := load_value as Vector2i + selected = find_resolution_value(resolution_value) + if selected == -1: + if add_nonstandard_value: + add_resolution(resolution_value) + selected = item_count - 1 + else: push_error("Setting value '%s' invalid for setting [%s] %s" % [load_value, section_name, setting_name]) + +func _on_item_selected(index): + Resolution.set_resolution(get_item_metadata(index)) diff --git a/game/src/OptionMenu/ScreenModeSelector.gd b/game/src/OptionMenu/ScreenModeSelector.gd index fae0771..92c5d60 100644 --- a/game/src/OptionMenu/ScreenModeSelector.gd +++ b/game/src/OptionMenu/ScreenModeSelector.gd @@ -24,9 +24,11 @@ func get_window_mode_from_screen_mode(screen_mode : int) -> Window.Mode: _: return Window.MODE_EXCLUSIVE_FULLSCREEN -func _on_item_selected(index : int): - print("Selected index: %d" % index) +func _setup_button(): + default_selected = get_screen_mode_from_window_mode(get_viewport().get_window().mode) + selected = default_selected +func _on_item_selected(index : int): var window := get_viewport().get_window() var current_resolution := Resolution.get_current_resolution() window.mode = get_window_mode_from_screen_mode(index) diff --git a/game/src/OptionMenu/SettingNodes/SettingHSlider.gd b/game/src/OptionMenu/SettingNodes/SettingHSlider.gd index da9348f..cf2adf4 100644 --- a/game/src/OptionMenu/SettingNodes/SettingHSlider.gd +++ b/game/src/OptionMenu/SettingNodes/SettingHSlider.gd @@ -10,10 +10,17 @@ var setting_name : String = "SettingHSlider" @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 value = file.get_value(section_name, setting_name, default_value) func save_setting(file : ConfigFile): + if file == null: return file.set_value(section_name, setting_name, value) func reset_setting(): diff --git a/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd b/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd index 46fc825..3a5c979 100644 --- a/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd +++ b/game/src/OptionMenu/SettingNodes/SettingOptionButton.gd @@ -8,15 +8,52 @@ var section_name : String = "Setting" var setting_name : String = "SettingOptionMenu" @export -var default_value : int = 0 +var default_selected : int = -1: + get: return default_selected + set(v): + if v == -1: + default_selected = -1 + return + default_selected = v % item_count -func load_setting(file : ConfigFile): - selected = file.get_value(section_name, setting_name, default_value) +@export +var add_nonstandard_value := false + +func _get_value_for_file(select_value : int): + if select_value > -1: + return get_item_text(select_value) + else: + return null + +func _set_value_from_file(load_value) -> void: + selected = -1 + for item_index in range(item_count): + if load_value == get_item_text(item_index): + selected = item_index + if selected == -1: + if add_nonstandard_value: + add_item(load_value) + selected = item_count - 1 + else: push_error("Setting value '%s' invalid for setting [%s] \"%s\"" % [load_value, section_name, setting_name]) + +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) + _setup_button() + +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))) item_selected.emit(selected) -func save_setting(file : ConfigFile): - file.set_value(section_name, setting_name, selected) +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(): - selected = default_value +func reset_setting() -> void: + selected = default_selected item_selected.emit(selected) diff --git a/game/src/OptionMenu/SoundTab.gd b/game/src/OptionMenu/SoundTab.gd new file mode 100644 index 0000000..c707605 --- /dev/null +++ b/game/src/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/OptionMenu/SoundTab.tscn b/game/src/OptionMenu/SoundTab.tscn new file mode 100644 index 0000000..10d7f10 --- /dev/null +++ b/game/src/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/OptionMenu/VideoTab.tscn b/game/src/OptionMenu/VideoTab.tscn new file mode 100644 index 0000000..d46f056 --- /dev/null +++ b/game/src/OptionMenu/VideoTab.tscn @@ -0,0 +1,73 @@ +[gd_scene load_steps=4 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/ScreenModeSelector.gd" id="2_wa7vw"] +[ext_resource type="Script" path="res://src/OptionMenu/MonitorDisplaySelector.gd" id="3_y6lyb"] + +[node name="Video" type="HBoxContainer"] +tooltip_text = "This is my cool and very nice tooltip" +alignment = 1 + +[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="ResolutionLabel" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Resolution" + +[node name="ResolutionSelector" type="OptionButton" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +item_count = 1 +selected = 0 +popup/item_0/text = "MISSING" +popup/item_0/id = 0 +script = ExtResource("1_i8nro") +section_name = "Video" +setting_name = "Resolution" +add_nonstandard_value = true + +[node name="ScreenModeLabel" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Screen Mode" + +[node name="ScreenModeSelector" type="OptionButton" parent="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("2_wa7vw") +section_name = "Video" +setting_name = "Mode Selected" + +[node name="MonitorSelectionLabel" type="Label" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +text = "Monitor Selection" + +[node name="MonitorDisplaySelector" type="OptionButton" parent="VBoxContainer/GridContainer"] +layout_mode = 2 +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" + +[connection signal="item_selected" from="VBoxContainer/GridContainer/ResolutionSelector" to="VBoxContainer/GridContainer/ResolutionSelector" method="_on_item_selected"] +[connection signal="item_selected" from="VBoxContainer/GridContainer/ScreenModeSelector" to="VBoxContainer/GridContainer/ScreenModeSelector" method="_on_item_selected"] +[connection signal="item_selected" from="VBoxContainer/GridContainer/MonitorDisplaySelector" to="VBoxContainer/GridContainer/MonitorDisplaySelector" method="_on_item_selected"] |