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