9const DataCacheManager = preload(
"res://scripts/systems/dataCacheManager/data_cache_manager.gd")
13var controller_active := false
14var last_controller_active := false
15var keyboard_active := false
16var data_cache_manager = DataCacheManager.new()
18const JOY_BUTTON_NAMES := {
23 "Back": JOY_BUTTON_BACK,
24 "Start": JOY_BUTTON_START,
25 "Left Shoulder": JOY_BUTTON_LEFT_SHOULDER,
26 "Right Shoulder": JOY_BUTTON_RIGHT_SHOULDER,
27 "Left Stick": JOY_BUTTON_LEFT_STICK,
28 "Right Stick": JOY_BUTTON_RIGHT_STICK,
29 "Dpad Up": JOY_BUTTON_DPAD_UP,
30 "Dpad Down": JOY_BUTTON_DPAD_DOWN,
31 "Dpad Left": JOY_BUTTON_DPAD_LEFT,
32 "Dpad Right": JOY_BUTTON_DPAD_RIGHT
35const JOY_AXIS_NAMES := {
36 "Left X": JOY_AXIS_LEFT_X,
37 "Left Y": JOY_AXIS_LEFT_Y,
38 "Right X": JOY_AXIS_RIGHT_X,
39 "Right Y": JOY_AXIS_RIGHT_Y,
40 "Trigger Left": JOY_AXIS_TRIGGER_LEFT,
41 "Trigger Right": JOY_AXIS_TRIGGER_RIGHT
47func load_actions(file_path:String) -> void:
48 var data = data_cache_manager.load_json(file_path)
49 if not data
is Dictionary:
52 for action
in data.get(
"actions", []):
53 if not action
is Dictionary:
55 var id = str(action.get(
"id",
""))
58 actions[id] = action.duplicate(true)
60 if action.has(
"keyboard"):
61 _add_key_binding(id, OS.find_keycode_from_string(str(action[
"keyboard"])))
62 for key_name
in action.get(
"keyboard_alt", []):
63 _add_key_binding(id, OS.find_keycode_from_string(str(key_name)))
64 for button_name
in action.get(
"joy_buttons", []):
65 bind_joy_button(id, _joy_button_from_value(button_name))
66 for axis_binding
in action.get(
"joy_axes", []):
67 if axis_binding
is Dictionary:
68 bind_joy_axis(id, _joy_axis_from_value(axis_binding.get(
"axis", 0)),
float(axis_binding.get(
"value", 1.0)))
69 if action.has(
"prompt"):
70 set_prompt(id,
"keyboard", str(action[
"prompt"]))
74func get_cache_stats() -> Dictionary:
75 return data_cache_manager.get_cache_stats()
79func get_actions(include_locked:bool = false) -> Array:
81 for id
in actions.keys():
82 if not include_locked
and not is_action_remappable(str(id)):
84 var action = actions[id].duplicate(true)
86 action[
"prompt"] = get_prompt(id)
92func is_action_remappable(action:String) -> bool:
93 var definition = actions.get(action, {})
94 if not definition
is Dictionary:
96 if str(action) ==
"game_pause" or str(action) ==
"game_settings":
98 return bool(definition.get(
"remappable", true))
102func ensure_action(action:String, deadzone:float = 0.5) -> void:
103 if not InputMap.has_action(action):
104 InputMap.add_action(action, deadzone)
108func bind_key(action:String, keycode:Key) -> void:
109 ensure_action(action)
110 InputMap.action_erase_events(action)
111 _add_key_binding(action, keycode)
112 set_prompt(action,
"keyboard", OS.get_keycode_string(keycode))
116func bind_from_event(action:String, event:InputEvent) -> bool:
117 note_input_event(event)
118 if not is_action_remappable(action):
120 if event
is InputEventKey
and event.pressed
and not event.echo:
121 bind_key(action, event.keycode)
123 if event
is InputEventMouseButton
and event.pressed:
124 bind_mouse_button(action, event.button_index)
125 set_prompt(action,
"keyboard",
"Mouse %d" % int(event.button_index))
127 if event
is InputEventJoypadButton
and event.pressed:
128 bind_joy_button(action, event.button_index)
129 set_prompt(action,
"controller",
"Button %d" % int(event.button_index))
131 if event
is InputEventJoypadMotion
and abs(event.axis_value) >= 0.5:
132 bind_joy_axis(action, event.axis, sign(event.axis_value))
133 set_prompt(action,
"controller",
"Axis %d %s" % [int(event.axis),
"+" if event.axis_value > 0.0
else "-"])
139func bind_joy_button(action:String, button_index:int) -> void:
140 ensure_action(action)
141 var event = InputEventJoypadButton.new()
142 event.button_index = button_index
143 _add_event_if_missing(action, event)
147func bind_joy_axis(action:String, axis:int, axis_value:float) -> void:
148 ensure_action(action)
149 var event = InputEventJoypadMotion.new()
151 event.axis_value = clamp(axis_value, -1.0, 1.0)
152 _add_event_if_missing(action, event)
156func bind_mouse_button(action:String, button_index:int) -> void:
157 ensure_action(action)
158 var event = InputEventMouseButton.new()
159 event.button_index = button_index
160 _add_event_if_missing(action, event)
164func is_pressed(action:String) -> bool:
165 return Input.is_action_pressed(action)
169func just_pressed(action:String) -> bool:
170 return Input.is_action_just_pressed(action)
174func note_input_event(event:InputEvent) -> void:
175 if event
is InputEventJoypadButton
and event.pressed:
176 controller_active = true
177 keyboard_active = false
178 elif event
is InputEventJoypadMotion
and abs(event.axis_value) >= 0.35:
179 controller_active = true
180 keyboard_active = false
181 elif event
is InputEventKey
and event.pressed:
182 controller_active = false
183 keyboard_active = true
184 elif event
is InputEventMouseButton
and event.pressed:
185 controller_active = false
186 keyboard_active = false
187 elif event
is InputEventScreenTouch
and event.pressed:
188 controller_active = false
189 keyboard_active = false
193func is_controller_active() -> bool:
194 return controller_active
or not Input.get_connected_joypads().is_empty()
198func is_controller_label_active() -> bool:
199 return controller_active
203func is_keyboard_active() -> bool:
204 return keyboard_active
208func consume_controller_mode_changed() -> bool:
209 var active = is_controller_active()
210 if active == last_controller_active:
212 last_controller_active = active
217func get_slot_label(slot_index:int, controller_label_set:String =
"xbox") -> String:
218 if is_controller_label_active():
219 var labels = _controller_slot_labels(controller_label_set)
220 if slot_index >= 0
and slot_index < labels.size():
221 return str(labels[slot_index])
222 return str(slot_index + 1)
226func get_slot_color(slot_index:int, controller_label_set:String =
"xbox") -> Color:
227 if not is_controller_label_active():
228 return Color(
"#f2c86b")
229 var colors = _controller_slot_colors(controller_label_set)
230 if slot_index >= 0
and slot_index < colors.size():
231 return Color(str(colors[slot_index]))
232 return Color(
"#f2c86b")
236func apply_controller_scroll(root:Node, delta:float, pixels_per_second:float = 720.0) -> bool:
237 var amount = Input.get_action_strength(
"ui_scroll_down") - Input.get_action_strength(
"ui_scroll_up")
238 if abs(amount) < 0.12:
240 var scroll = _find_scroll_target(root)
243 scroll.scroll_vertical = clampi(
244 scroll.scroll_vertical + int(amount * pixels_per_second * delta),
246 scroll.get_v_scroll_bar().max_value
253func update_input_module(root:Node, delta:float, onscreen_controls = null, user_onscreen_enabled:bool = true, scroll_speed:float = 720.0) -> bool:
254 var mode_changed = consume_controller_mode_changed()
255 if onscreen_controls != null
and onscreen_controls.has_method(
"set_visible"):
256 var desired_visible := user_onscreen_enabled
and not is_controller_active()
and not is_keyboard_active()
257 if not onscreen_controls.has_method(
"is_visible")
or bool(onscreen_controls.is_visible()) != desired_visible:
258 onscreen_controls.set_visible(desired_visible)
259 apply_controller_scroll(root, delta, scroll_speed)
264func set_prompt(action:String, device:String, text:String) -> void:
265 if not prompts.has(action):
267 prompts[action][device] = text
271func get_prompt(action:String, device:String =
"keyboard") -> String:
272 return str(prompts.get(action, {}).get(device, action))
277func get_state() -> Dictionary:
279 "prompts": prompts.duplicate(true),
280 "actions": actions.duplicate(true),
281 "bindings": _get_binding_state()
286func apply_state(state:Dictionary) -> void:
289 prompts = state.get(
"prompts", prompts).duplicate(true)
290 if state.has(
"actions")
and not state.get(
"actions", {}).is_empty():
291 var saved_actions = state.get(
"actions", {})
292 for action_id
in saved_actions.keys():
293 if actions.has(action_id)
and saved_actions[action_id]
is Dictionary:
294 var merged = actions[action_id].duplicate(true)
295 for key
in saved_actions[action_id].keys():
296 merged[key] = saved_actions[action_id][key]
297 actions[action_id] = merged
299 actions[action_id] = saved_actions[action_id]
300 for action
in state.get(
"bindings", {}).keys():
301 _apply_action_bindings(str(action), state[
"bindings"][action])
304func _get_binding_state() -> Dictionary:
306 for action
in actions.keys():
307 if not is_action_remappable(str(action)):
309 var action_bindings = []
310 if not InputMap.has_action(str(action)):
312 for event
in InputMap.action_get_events(str(action)):
313 if event
is InputEventKey:
314 action_bindings.append({
"type":
"key",
"keycode": event.keycode})
315 elif event
is InputEventMouseButton:
316 action_bindings.append({
"type":
"mouse",
"button_index": event.button_index})
317 elif event
is InputEventJoypadButton:
318 action_bindings.append({
"type":
"joy_button",
"button_index": event.button_index})
319 elif event
is InputEventJoypadMotion:
320 action_bindings.append({
"type":
"joy_axis",
"axis": event.axis,
"axis_value": event.axis_value})
321 bindings[action] = action_bindings
325func _apply_action_bindings(action:String, action_bindings:Array) -> void:
326 ensure_action(action)
327 if not is_action_remappable(action):
328 InputMap.action_erase_events(action)
329 _ensure_definition_defaults(action)
331 InputMap.action_erase_events(action)
332 for binding
in action_bindings:
333 if not binding
is Dictionary:
335 match str(binding.get(
"type",
"")):
337 var event = InputEventKey.new()
338 event.keycode = int(binding.get(
"keycode", 0))
339 InputMap.action_add_event(action, event)
341 var event = InputEventMouseButton.new()
342 event.button_index = int(binding.get(
"button_index", 0))
343 InputMap.action_add_event(action, event)
345 var event = InputEventJoypadButton.new()
346 event.button_index = int(binding.get(
"button_index", 0))
347 InputMap.action_add_event(action, event)
349 var event = InputEventJoypadMotion.new()
350 event.axis = int(binding.get(
"axis", 0))
351 event.axis_value =
float(binding.get(
"axis_value", 1.0))
352 InputMap.action_add_event(action, event)
353 _ensure_definition_defaults(action)
356func _add_key_binding(action:String, keycode:Key) -> void:
357 if keycode == KEY_NONE:
359 var event = InputEventKey.new()
360 event.keycode = keycode
361 _add_event_if_missing(action, event)
364func _add_event_if_missing(action:String, event:InputEvent) -> void:
365 for existing
in InputMap.action_get_events(action):
366 if _events_match(existing, event):
368 InputMap.action_add_event(action, event)
371func _events_match(left:InputEvent, right:InputEvent) -> bool:
372 if left.get_class() != right.get_class():
374 if left
is InputEventKey
and right
is InputEventKey:
375 return left.keycode == right.keycode
376 if left
is InputEventMouseButton
and right
is InputEventMouseButton:
377 return left.button_index == right.button_index
378 if left
is InputEventJoypadButton
and right
is InputEventJoypadButton:
379 return left.button_index == right.button_index
380 if left
is InputEventJoypadMotion
and right
is InputEventJoypadMotion:
381 return left.axis == right.axis
and sign(left.axis_value) == sign(right.axis_value)
385func _ensure_definition_defaults(action_id:String) -> void:
386 var definition = actions.get(action_id, {})
387 if not definition
is Dictionary:
389 if definition.has(
"keyboard"):
390 _add_key_binding(action_id, OS.find_keycode_from_string(str(definition[
"keyboard"])))
391 for key_name
in definition.get(
"keyboard_alt", []):
392 _add_key_binding(action_id, OS.find_keycode_from_string(str(key_name)))
393 for button_name
in definition.get(
"joy_buttons", []):
394 bind_joy_button(action_id, _joy_button_from_value(button_name))
395 for axis_binding
in definition.get(
"joy_axes", []):
396 if axis_binding
is Dictionary:
397 bind_joy_axis(action_id, _joy_axis_from_value(axis_binding.get(
"axis", 0)),
float(axis_binding.get(
"value", 1.0)))
400func _joy_button_from_value(value) -> int:
403 return int(JOY_BUTTON_NAMES.get(str(value), int(value)
if str(value).is_valid_int()
else JOY_BUTTON_A))
406func _joy_axis_from_value(value) -> int:
409 return int(JOY_AXIS_NAMES.get(str(value), int(value)
if str(value).is_valid_int()
else JOY_AXIS_LEFT_X))
412func _controller_slot_labels(controller_label_set:String) -> Array:
413 match controller_label_set:
415 return [
"B",
"Y",
"X",
"A",
"LS"]
417 return [
"Cross",
"Square",
"Triangle",
"Circle",
"L3"]
418 return [
"A",
"X",
"Y",
"B",
"LS"]
421func _controller_slot_colors(controller_label_set:String) -> Array:
422 match controller_label_set:
424 return [
"#f05f64",
"#55d17a",
"#4aa3ff",
"#f2c86b",
"#aab6c4"]
426 return [
"#55d17a",
"#4aa3ff",
"#f2c86b",
"#f05f64",
"#aab6c4"]
427 return [
"#55d17a",
"#4aa3ff",
"#f2c86b",
"#f05f64",
"#aab6c4"]
430func _find_scroll_target(root:Node) -> ScrollContainer:
433 var viewport = root.get_viewport()
if root
is Control
else null
435 var focus = viewport.gui_get_focus_owner()
436 var focused_scroll = _ancestor_scroll_container(focus)
437 if _scroll_can_move(focused_scroll):
438 return focused_scroll
439 var visible_scrolls = []
440 _collect_visible_scrolls(root, visible_scrolls)
441 for scroll
in visible_scrolls:
442 if _scroll_can_move(scroll):
447func _ancestor_scroll_container(node:Node) -> ScrollContainer:
449 while current != null:
450 if current
is ScrollContainer:
452 current = current.get_parent()
456func _collect_visible_scrolls(node:Node, results:Array) -> void:
457 if node
is Control
and not node.is_visible_in_tree():
459 if node
is ScrollContainer:
461 for child
in node.get_children():
462 _collect_visible_scrolls(child, results)
465func _scroll_can_move(scroll:ScrollContainer) -> bool:
466 if scroll == null
or not scroll.is_visible_in_tree():
468 var bar = scroll.get_v_scroll_bar()
469 return bar != null
and bar.max_value > 0.0
and scroll.vertical_scroll_mode != ScrollContainer.SCROLL_MODE_DISABLED