aboutsummaryrefslogtreecommitdiff
path: root/game/addons/keychain/ShortcutSelectorDialog.gd
blob: fb28434b02a59087574a32e67b833127ef0b172d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
extends ConfirmationDialog

enum InputTypes { KEYBOARD, MOUSE, JOY_BUTTON, JOY_AXIS }

@export var input_type := InputTypes.KEYBOARD
var listened_input: InputEvent

@onready var root := get_parent()
@onready var input_type_l := $VBoxContainer/InputTypeLabel as Label
@onready var entered_shortcut := $VBoxContainer/EnteredShortcut as LineEdit
@onready var option_button := $VBoxContainer/OptionButton as OptionButton
@onready var modifier_buttons := $VBoxContainer/ModifierButtons as HBoxContainer
@onready var alt_button := $VBoxContainer/ModifierButtons/Alt as CheckBox
@onready var shift_button := $VBoxContainer/ModifierButtons/Shift as CheckBox
@onready var control_button := $VBoxContainer/ModifierButtons/Control as CheckBox
@onready var meta_button := $VBoxContainer/ModifierButtons/Meta as CheckBox
@onready var command_control_button := $VBoxContainer/ModifierButtons/CommandOrControl as CheckBox
@onready var already_exists := $VBoxContainer/AlreadyExistsLabel as Label


func _ready() -> void:
   set_process_input(false)
   if input_type == InputTypes.KEYBOARD:
      entered_shortcut.visible = true
      option_button.visible = false
      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:
      if input_type != InputTypes.MOUSE:
         modifier_buttons.visible = false
      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
      _set_modifier_buttons_state(listened_input)
      entered_shortcut.text = event.as_text()
      _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 = tree_item.get_next_in_tree()

   Keychain.action_add_event(action, new_event)
   Keychain.selected_profile.change_action(action)
   return true


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:
   var metadata = root.currently_editing_tree_item.get_metadata(0)
   if input_type == InputTypes.KEYBOARD:
      listened_input = null
      already_exists.text = ""
      entered_shortcut.text = ""
      if metadata is InputEvent:
         _set_modifier_buttons_state(metadata)
      await get_tree().process_frame
      entered_shortcut.grab_focus()
   else:
      if metadata is InputEvent:  # Editing an input event
         var index := 0
         if metadata is InputEventMouseButton:
            index = metadata.button_index - 1
            _set_modifier_buttons_state(metadata)
         elif metadata is InputEventJoypadButton:
            index = metadata.button_index
         elif metadata is InputEventJoypadMotion:
            index = metadata.axis * 2
            index += signi(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
      listened_input.alt_pressed = alt_button.button_pressed
      listened_input.shift_pressed = shift_button.button_pressed
      listened_input.ctrl_pressed = control_button.button_pressed
      listened_input.meta_pressed = meta_button.button_pressed
      listened_input.command_or_control_autoremap = command_control_button.button_pressed
   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)


func _on_alt_toggled(button_pressed: bool) -> void:
   if not is_instance_valid(listened_input):
      return
   listened_input.alt_pressed = button_pressed
   entered_shortcut.text = listened_input.as_text()


func _set_modifier_buttons_state(event: InputEventWithModifiers) -> void:
   alt_button.button_pressed = event.alt_pressed
   shift_button.button_pressed = event.shift_pressed
   control_button.button_pressed = event.ctrl_pressed
   meta_button.button_pressed = event.meta_pressed
   command_control_button.button_pressed = event.command_or_control_autoremap


func _on_shift_toggled(button_pressed: bool) -> void:
   if not is_instance_valid(listened_input):
      return
   listened_input.shift_pressed = button_pressed
   entered_shortcut.text = listened_input.as_text()


func _on_control_toggled(button_pressed: bool) -> void:
   if not is_instance_valid(listened_input):
      return
   listened_input.ctrl_pressed = button_pressed
   entered_shortcut.text = listened_input.as_text()


func _on_meta_toggled(button_pressed: bool) -> void:
   if not is_instance_valid(listened_input):
      return
   listened_input.meta_pressed = button_pressed
   entered_shortcut.text = listened_input.as_text()


func _on_command_or_control_toggled(button_pressed: bool) -> void:
   control_button.button_pressed = false
   meta_button.button_pressed = false
   control_button.visible = not button_pressed
   meta_button.visible = not button_pressed
   if not is_instance_valid(listened_input):
      return
   listened_input.command_or_control_autoremap = button_pressed
   entered_shortcut.text = listened_input.as_text()