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 preferred_input_mode :=
"auto"
17var data_cache_manager = DataCacheManager.new()
19const JOY_BUTTON_NAMES := {
24 "Back": JOY_BUTTON_BACK,
25 "Start": JOY_BUTTON_START,
26 "Left Shoulder": JOY_BUTTON_LEFT_SHOULDER,
27 "Right Shoulder": JOY_BUTTON_RIGHT_SHOULDER,
28 "Left Stick": JOY_BUTTON_LEFT_STICK,
29 "Right Stick": JOY_BUTTON_RIGHT_STICK,
30 "Dpad Up": JOY_BUTTON_DPAD_UP,
31 "Dpad Down": JOY_BUTTON_DPAD_DOWN,
32 "Dpad Left": JOY_BUTTON_DPAD_LEFT,
33 "Dpad Right": JOY_BUTTON_DPAD_RIGHT
36const JOY_AXIS_NAMES := {
37 "Left X": JOY_AXIS_LEFT_X,
38 "Left Y": JOY_AXIS_LEFT_Y,
39 "Right X": JOY_AXIS_RIGHT_X,
40 "Right Y": JOY_AXIS_RIGHT_Y,
41 "Trigger Left": JOY_AXIS_TRIGGER_LEFT,
42 "Trigger Right": JOY_AXIS_TRIGGER_RIGHT
48func load_actions(file_path:String) -> void:
49 var data = data_cache_manager.load_json(file_path)
50 if not data
is Dictionary:
53 for action
in data.get(
"actions", []):
54 if not action
is Dictionary:
56 var id = str(action.get(
"id",
""))
59 actions[id] = action.duplicate(true)
61 if action.has(
"keyboard"):
62 _add_key_binding(id, OS.find_keycode_from_string(str(action[
"keyboard"])))
63 for key_name
in action.get(
"keyboard_alt", []):
64 _add_key_binding(id, OS.find_keycode_from_string(str(key_name)))
65 for button_name
in action.get(
"joy_buttons", []):
66 bind_joy_button(id, _joy_button_from_value(button_name))
67 for axis_binding
in action.get(
"joy_axes", []):
68 if axis_binding
is Dictionary:
69 bind_joy_axis(id, _joy_axis_from_value(axis_binding.get(
"axis", 0)),
float(axis_binding.get(
"value", 1.0)))
70 if action.has(
"prompt"):
71 set_prompt(id,
"keyboard", str(action[
"prompt"]))
75func get_cache_stats() -> Dictionary:
76 return data_cache_manager.get_cache_stats()
80func get_actions(include_locked:bool = false) -> Array:
82 for id
in actions.keys():
83 if not include_locked
and not is_action_remappable(str(id)):
85 var action = actions[id].duplicate(true)
87 action[
"prompt"] = get_prompt(id)
93func is_action_remappable(action:String) -> bool:
94 var definition = actions.get(action, {})
95 if not definition
is Dictionary:
97 if str(action) ==
"game_pause" or str(action) ==
"game_settings":
99 return bool(definition.get(
"remappable", true))
103func ensure_action(action:String, deadzone:float = 0.5) -> void:
104 if not InputMap.has_action(action):
105 InputMap.add_action(action, deadzone)
109func bind_key(action:String, keycode:Key) -> void:
110 ensure_action(action)
111 InputMap.action_erase_events(action)
112 _add_key_binding(action, keycode)
113 set_prompt(action,
"keyboard", OS.get_keycode_string(keycode))
117func bind_from_event(action:String, event:InputEvent) -> bool:
118 note_input_event(event)
119 if not is_action_remappable(action):
121 if event
is InputEventKey
and event.pressed
and not event.echo:
122 bind_key(action, event.keycode)
124 if event
is InputEventMouseButton
and event.pressed:
125 bind_mouse_button(action, event.button_index)
126 set_prompt(action,
"keyboard",
"Mouse %d" % int(event.button_index))
128 if event
is InputEventJoypadButton
and event.pressed:
129 bind_joy_button(action, event.button_index)
130 set_prompt(action,
"controller",
"Button %d" % int(event.button_index))
132 if event
is InputEventJoypadMotion
and abs(event.axis_value) >= 0.5:
133 bind_joy_axis(action, event.axis, sign(event.axis_value))
134 set_prompt(action,
"controller",
"Axis %d %s" % [int(event.axis),
"+" if event.axis_value > 0.0
else "-"])
140func bind_joy_button(action:String, button_index:int) -> void:
141 ensure_action(action)
142 var event = InputEventJoypadButton.new()
143 event.button_index = button_index
144 _add_event_if_missing(action, event)
148func bind_joy_axis(action:String, axis:int, axis_value:float) -> void:
149 ensure_action(action)
150 var event = InputEventJoypadMotion.new()
152 event.axis_value = clamp(axis_value, -1.0, 1.0)
153 _add_event_if_missing(action, event)
157func bind_mouse_button(action:String, button_index:int) -> void:
158 ensure_action(action)
159 var event = InputEventMouseButton.new()
160 event.button_index = button_index
161 _add_event_if_missing(action, event)
165func is_pressed(action:String) -> bool:
166 return Input.is_action_pressed(action)
170func just_pressed(action:String) -> bool:
171 return Input.is_action_just_pressed(action)
175func note_input_event(event:InputEvent) -> void:
176 if event
is InputEventJoypadButton
and event.pressed:
177 controller_active = true
178 keyboard_active = false
179 elif event
is InputEventJoypadMotion
and abs(event.axis_value) >= 0.35:
180 controller_active = true
181 keyboard_active = false
182 elif event
is InputEventKey
and event.pressed:
183 controller_active = false
184 keyboard_active = true
185 elif event
is InputEventMouseButton
and event.pressed:
186 controller_active = false
187 keyboard_active = false
188 elif event
is InputEventScreenTouch
and event.pressed:
189 controller_active = false
190 keyboard_active = false
194func set_input_mode(mode:String) -> void:
195 preferred_input_mode = mode
if mode
in [
"auto",
"touch",
"keyboard",
"controller"]
else "auto"
198func get_active_input_mode() -> String:
199 if preferred_input_mode !=
"auto":
200 return preferred_input_mode
201 if controller_active:
205 if DisplayServer.is_touchscreen_available():
207 if not Input.get_connected_joypads().is_empty():
212func is_touch_active() -> bool:
213 return get_active_input_mode() ==
"touch"
217func is_controller_active() -> bool:
218 return get_active_input_mode() ==
"controller"
222func is_controller_label_active() -> bool:
223 return get_active_input_mode() ==
"controller"
227func is_keyboard_active() -> bool:
228 return get_active_input_mode() ==
"keyboard"
232func get_controller_label_set() -> String:
233 var joypads := Input.get_connected_joypads()
234 var joy_name := Input.get_joy_name(joypads[0]).to_lower()
if not joypads.is_empty()
else ""
235 if "playstation" in joy_name
or "dualsense" in joy_name
or "dualshock" in joy_name:
237 if "nintendo" in joy_name
or "switch" in joy_name:
243func format_active_prompt(keyboard:String, xbox:String, playstation:String =
"", nintendo:String =
"") -> String:
244 if not is_controller_label_active():
246 match get_controller_label_set():
247 "playstation":
return playstation
if playstation !=
"" else xbox
248 "nintendo":
return nintendo
if nintendo !=
"" else xbox
253func consume_controller_mode_changed() -> bool:
254 var active = is_controller_active()
255 if active == last_controller_active:
257 last_controller_active = active
262func get_slot_label(slot_index:int, controller_label_set:String =
"xbox") -> String:
263 if is_controller_label_active():
264 var labels = _controller_slot_labels(controller_label_set)
265 if slot_index >= 0
and slot_index < labels.size():
266 return str(labels[slot_index])
267 return str(slot_index + 1)
271func get_slot_color(slot_index:int, controller_label_set:String =
"xbox") -> Color:
272 if not is_controller_label_active():
273 return Color(
"#f2c86b")
274 var colors = _controller_slot_colors(controller_label_set)
275 if slot_index >= 0
and slot_index < colors.size():
276 return Color(str(colors[slot_index]))
277 return Color(
"#f2c86b")
281func apply_controller_scroll(root:Node, delta:float, pixels_per_second:float = 720.0) -> bool:
282 var down_strength:float = Input.get_action_strength(
"ui_scroll_down")
if InputMap.has_action(
"ui_scroll_down")
else 0.0
283 var up_strength:float = Input.get_action_strength(
"ui_scroll_up")
if InputMap.has_action(
"ui_scroll_up")
else 0.0
284 var amount:float = down_strength - up_strength
285 if abs(amount) < 0.12:
287 var scroll = _find_scroll_target(root)
290 scroll.scroll_vertical = clampi(
291 scroll.scroll_vertical + int(amount * pixels_per_second * delta),
293 scroll.get_v_scroll_bar().max_value
300func update_input_module(root:Node, delta:float, onscreen_controls = null, user_onscreen_enabled:bool = true, scroll_speed:float = 720.0) -> bool:
301 var mode_changed = consume_controller_mode_changed()
302 if onscreen_controls != null
and onscreen_controls.has_method(
"set_visible"):
303 var desired_visible := user_onscreen_enabled
and is_touch_active()
304 if not onscreen_controls.has_method(
"is_visible")
or bool(onscreen_controls.is_visible()) != desired_visible:
305 onscreen_controls.set_visible(desired_visible)
306 apply_controller_scroll(root, delta, scroll_speed)
311func set_prompt(action:String, device:String, text:String) -> void:
312 if not prompts.has(action):
314 prompts[action][device] = text
318func get_prompt(action:String, device:String =
"keyboard") -> String:
319 return str(prompts.get(action, {}).get(device, action))
324func get_state() -> Dictionary:
326 "prompts": prompts.duplicate(true),
327 "actions": actions.duplicate(true),
328 "bindings": _get_binding_state()
333func apply_state(state:Dictionary) -> void:
336 prompts = state.get(
"prompts", prompts).duplicate(true)
337 if state.has(
"actions")
and not state.get(
"actions", {}).is_empty():
338 var saved_actions = state.get(
"actions", {})
339 for action_id
in saved_actions.keys():
340 if actions.has(action_id)
and saved_actions[action_id]
is Dictionary:
341 var merged = actions[action_id].duplicate(true)
342 for key
in saved_actions[action_id].keys():
343 merged[key] = saved_actions[action_id][key]
344 actions[action_id] = merged
346 actions[action_id] = saved_actions[action_id]
347 for action
in state.get(
"bindings", {}).keys():
348 _apply_action_bindings(str(action), state[
"bindings"][action])
351func _get_binding_state() -> Dictionary:
353 for action
in actions.keys():
354 if not is_action_remappable(str(action)):
356 var action_bindings = []
357 if not InputMap.has_action(str(action)):
359 for event
in InputMap.action_get_events(str(action)):
360 if event
is InputEventKey:
361 action_bindings.append({
"type":
"key",
"keycode": event.keycode})
362 elif event
is InputEventMouseButton:
363 action_bindings.append({
"type":
"mouse",
"button_index": event.button_index})
364 elif event
is InputEventJoypadButton:
365 action_bindings.append({
"type":
"joy_button",
"button_index": event.button_index})
366 elif event
is InputEventJoypadMotion:
367 action_bindings.append({
"type":
"joy_axis",
"axis": event.axis,
"axis_value": event.axis_value})
368 bindings[action] = action_bindings
372func _apply_action_bindings(action:String, action_bindings:Array) -> void:
373 ensure_action(action)
374 if not is_action_remappable(action):
375 InputMap.action_erase_events(action)
376 _ensure_definition_defaults(action)
378 InputMap.action_erase_events(action)
379 for binding
in action_bindings:
380 if not binding
is Dictionary:
382 match str(binding.get(
"type",
"")):
384 var event = InputEventKey.new()
385 event.keycode = int(binding.get(
"keycode", 0))
386 InputMap.action_add_event(action, event)
388 var event = InputEventMouseButton.new()
389 event.button_index = int(binding.get(
"button_index", 0))
390 InputMap.action_add_event(action, event)
392 var event = InputEventJoypadButton.new()
393 event.button_index = int(binding.get(
"button_index", 0))
394 InputMap.action_add_event(action, event)
396 var event = InputEventJoypadMotion.new()
397 event.axis = int(binding.get(
"axis", 0))
398 event.axis_value =
float(binding.get(
"axis_value", 1.0))
399 InputMap.action_add_event(action, event)
400 _ensure_definition_defaults(action)
403func _add_key_binding(action:String, keycode:Key) -> void:
404 if keycode == KEY_NONE:
406 var event = InputEventKey.new()
407 event.keycode = keycode
408 _add_event_if_missing(action, event)
411func _add_event_if_missing(action:String, event:InputEvent) -> void:
412 for existing
in InputMap.action_get_events(action):
413 if _events_match(existing, event):
415 InputMap.action_add_event(action, event)
418func _events_match(left:InputEvent, right:InputEvent) -> bool:
419 if left.get_class() != right.get_class():
421 if left
is InputEventKey
and right
is InputEventKey:
422 return left.keycode == right.keycode
423 if left
is InputEventMouseButton
and right
is InputEventMouseButton:
424 return left.button_index == right.button_index
425 if left
is InputEventJoypadButton
and right
is InputEventJoypadButton:
426 return left.button_index == right.button_index
427 if left
is InputEventJoypadMotion
and right
is InputEventJoypadMotion:
428 return left.axis == right.axis
and sign(left.axis_value) == sign(right.axis_value)
432func _ensure_definition_defaults(action_id:String) -> void:
433 var definition = actions.get(action_id, {})
434 if not definition
is Dictionary:
436 if definition.has(
"keyboard"):
437 _add_key_binding(action_id, OS.find_keycode_from_string(str(definition[
"keyboard"])))
438 for key_name
in definition.get(
"keyboard_alt", []):
439 _add_key_binding(action_id, OS.find_keycode_from_string(str(key_name)))
440 for button_name
in definition.get(
"joy_buttons", []):
441 bind_joy_button(action_id, _joy_button_from_value(button_name))
442 for axis_binding
in definition.get(
"joy_axes", []):
443 if axis_binding
is Dictionary:
444 bind_joy_axis(action_id, _joy_axis_from_value(axis_binding.get(
"axis", 0)),
float(axis_binding.get(
"value", 1.0)))
447func _joy_button_from_value(value) -> int:
450 return int(JOY_BUTTON_NAMES.get(str(value), int(value)
if str(value).is_valid_int()
else JOY_BUTTON_A))
453func _joy_axis_from_value(value) -> int:
456 return int(JOY_AXIS_NAMES.get(str(value), int(value)
if str(value).is_valid_int()
else JOY_AXIS_LEFT_X))
459func _controller_slot_labels(controller_label_set:String) -> Array:
460 match controller_label_set:
462 return [
"B",
"Y",
"X",
"A",
"LS"]
464 return [
"Cross",
"Square",
"Triangle",
"Circle",
"L3"]
465 return [
"A",
"X",
"Y",
"B",
"LS"]
468func _controller_slot_colors(controller_label_set:String) -> Array:
469 match controller_label_set:
471 return [
"#f05f64",
"#55d17a",
"#4aa3ff",
"#f2c86b",
"#aab6c4"]
473 return [
"#55d17a",
"#4aa3ff",
"#f2c86b",
"#f05f64",
"#aab6c4"]
474 return [
"#55d17a",
"#4aa3ff",
"#f2c86b",
"#f05f64",
"#aab6c4"]
477func _find_scroll_target(root:Node) -> ScrollContainer:
480 var viewport = root.get_viewport()
if root
is Control
else null
482 var focus = viewport.gui_get_focus_owner()
483 var focused_scroll = _ancestor_scroll_container(focus)
484 if _scroll_can_move(focused_scroll):
485 return focused_scroll
486 var visible_scrolls = []
487 _collect_visible_scrolls(root, visible_scrolls)
488 for scroll
in visible_scrolls:
489 if _scroll_can_move(scroll):
494func _ancestor_scroll_container(node:Node) -> ScrollContainer:
496 while current != null:
497 if current
is ScrollContainer:
499 current = current.get_parent()
503func _collect_visible_scrolls(node:Node, results:Array) -> void:
504 if node
is Control
and not node.is_visible_in_tree():
506 if node
is ScrollContainer:
508 for child
in node.get_children():
509 _collect_visible_scrolls(child, results)
512func _scroll_can_move(scroll:ScrollContainer) -> bool:
513 if scroll == null
or not scroll.is_visible_in_tree():
515 var bar = scroll.get_v_scroll_bar()
516 return bar != null
and bar.max_value > 0.0
and scroll.vertical_scroll_mode != ScrollContainer.SCROLL_MODE_DISABLED