aboutsummaryrefslogtreecommitdiff
path: root/game/addons/keychain/ShortcutSelectorDialog.gd
diff options
context:
space:
mode:
Diffstat (limited to 'game/addons/keychain/ShortcutSelectorDialog.gd')
-rw-r--r--game/addons/keychain/ShortcutSelectorDialog.gd199
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)