diff options
Diffstat (limited to 'game/addons/keychain/ShortcutEdit.gd')
-rw-r--r-- | game/addons/keychain/ShortcutEdit.gd | 416 |
1 files changed, 416 insertions, 0 deletions
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) |