aboutsummaryrefslogtreecommitdiff
path: root/game/addons/keychain/Keychain.gd
blob: 2288107b24ec4fd9ea2e678f3cda22a99004b2f8 (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
extends Node

const PROFILES_PATH := "user://shortcut_profiles"

## [Array] of [ShortcutProfile]s.
var profiles: Array[ShortcutProfile] = [preload("profiles/default.tres")]
var selected_profile := profiles[0]  ## The currently selected [ShortcutProfile].
var profile_index := 0  ## The index of the currently selected [ShortcutProfile].
## [Dictionary] of [String] and [InputAction].
## Syntax: "action_name": InputAction.new("Action Display Name", "Group", true)
## Note that "action_name" must already exist in the Project's Input Map.
var actions := {}
## [Dictionary] of [String] and [InputGroup].
## Syntax: "Group Name": InputGroup.new("Parent Group Name")
var groups := {}
var ignore_actions: Array[StringName] = []  ## [Array] of [StringName] input map actions to ignore.
## If [code]true[/code], ignore Godot's default "ui_" input map actions.
var ignore_ui_actions := true
## A [PackedByteArray] of [bool]s with a fixed length of 4. Used for developers to allow or
## forbid setting certain types of InputEvents. The first element is for [InputEventKey]s,
## the second for [InputEventMouseButton]s, the third for [InputEventJoypadButton]s
## and the fourth for [InputEventJoypadMotion]s.
var changeable_types: PackedByteArray = [true, true, true, true]
## The file path of the [code]config_file[/code].
var config_path := "user://cache.ini"
## Used to store the settings to the filesystem.
var config_file: ConfigFile


class InputAction:
   var display_name := ""
   var group := ""
   var global := true

   func _init(_display_name := "", _group := "", _global := true):
      display_name = _display_name
      group = _group
      global = _global


class InputGroup:
   var parent_group := ""
   var folded := true
   var tree_item: TreeItem

   func _init(_parent_group := "", _folded := true) -> void:
      parent_group = _parent_group
      folded = _folded


func _ready() -> void:
   if !config_file:
      config_file = ConfigFile.new()
      if !config_path.is_empty():
         config_file.load(config_path)

   # Load shortcut profiles
   DirAccess.make_dir_recursive_absolute(PROFILES_PATH)
   var profile_dir := DirAccess.open(PROFILES_PATH)
   profile_dir.list_dir_begin()
   var file_name = profile_dir.get_next()
   while file_name != "":
      if !profile_dir.current_is_dir():
         if file_name.get_extension() == "tres":
            var file = load(PROFILES_PATH.path_join(file_name))
            if file is ShortcutProfile:
               profiles.append(file)
      file_name = profile_dir.get_next()

   # If there are no profiles besides the default, create one custom
   if profiles.size() == 1:
      var profile := ShortcutProfile.new()
      profile.name = "Custom"
      profile.resource_path = PROFILES_PATH.path_join("custom.tres")
      var saved := profile.save()
      if saved:
         profiles.append(profile)

   for profile in profiles:
      profile.fill_bindings()

   profile_index = config_file.get_value("shortcuts", "shortcuts_profile", 0)
   change_profile(profile_index)


func change_profile(index: int) -> void:
   if index >= profiles.size():
      index = profiles.size() - 1
   profile_index = index
   selected_profile = profiles[index]
   for action in selected_profile.bindings:
      action_erase_events(action)
      for event in selected_profile.bindings[action]:
         action_add_event(action, event)


func action_add_event(action: StringName, event: InputEvent) -> void:
   InputMap.action_add_event(action, event)


func action_erase_event(action: StringName, event: InputEvent) -> void:
   InputMap.action_erase_event(action, event)


func action_erase_events(action: StringName) -> void:
   InputMap.action_erase_events(action)


func load_translation(locale: String) -> void:
   var translation = load("res://addons/keychain/translations".path_join(locale + ".po"))
   if is_instance_valid(translation) and translation is Translation:
      TranslationServer.add_translation(translation)


## Converts a [param text] with snake case to a more readable format, by replacing
## underscores with spaces. If [param capitalize_first_letter] is [code]true[/code],
## the first letter of the text is capitalized.
## E.g, "snake_case" would be converted to "Snake case" if
## [param capitalize_first_letter] is [code]true[/code], else it would be converted to
## "snake case".
func humanize_snake_case(text: String, capitalize_first_letter := true) -> String:
   text = text.replace("_", " ")
   if capitalize_first_letter:
      var first_letter := text.left(1)
      first_letter = first_letter.capitalize()
      text = text.right(-1)
      text = text.insert(0, first_letter)
   return text