aboutsummaryrefslogtreecommitdiff
path: root/game/addons/keychain
diff options
context:
space:
mode:
author George L. Albany <Megacake1234@gmail.com>2024-05-04 23:56:36 +0200
committer GitHub <noreply@github.com>2024-05-04 23:56:36 +0200
commit8c8ee1524f51d44acd1d1894eda5984956cba9a6 (patch)
tree4952dc83ebdf9f3bfffbdc9240eead87b8eac70e /game/addons/keychain
parentbf3df0ee900f406a5a2aa56609ecb89c67055351 (diff)
parent6bc32dc48c775223310e757072beab2a4bb0e9b4 (diff)
Merge pull request #225 from Spartan322/update/keychain
Update Keychain to Orama-Interactive/Keychain@5031af5
Diffstat (limited to 'game/addons/keychain')
-rw-r--r--game/addons/keychain/Keychain.gd174
-rw-r--r--game/addons/keychain/ShortcutEdit.gd44
-rw-r--r--game/addons/keychain/ShortcutEdit.tscn12
-rw-r--r--game/addons/keychain/ShortcutSelectorDialog.gd101
-rw-r--r--game/addons/keychain/ShortcutSelectorDialog.tscn49
5 files changed, 177 insertions, 203 deletions
diff --git a/game/addons/keychain/Keychain.gd b/game/addons/keychain/Keychain.gd
index ff939f3..2288107 100644
--- a/game/addons/keychain/Keychain.gd
+++ b/game/addons/keychain/Keychain.gd
@@ -1,22 +1,29 @@
extends Node
-const TRANSLATIONS_PATH := "res://addons/keychain/translations"
const PROFILES_PATH := "user://shortcut_profiles"
-# Change these settings
-var profiles := [preload("profiles/default.tres")]
-var selected_profile: ShortcutProfile = profiles[0]
-var profile_index := 0
-# Syntax: "action_name": InputAction.new("Action Display Name", "Group", true)
-# Note that "action_name" must already exist in the Project's Input Map.
+## [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 := {}
-# Syntax: "Group Name": InputGroup.new("Parent Group Name")
+## [Dictionary] of [String] and [InputGroup].
+## Syntax: "Group Name": InputGroup.new("Parent Group Name")
var groups := {}
-var ignore_actions := []
+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
-var changeable_types := [true, true, true, true]
-var multiple_menu_accelerators := false
+## 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
@@ -25,79 +32,11 @@ class InputAction:
var group := ""
var global := true
- func _init(_display_name := "",_group := "",_global := true):
+ func _init(_display_name := "", _group := "", _global := true):
display_name = _display_name
group = _group
global = _global
- func update_node(_action: String) -> void:
- pass
-
- func handle_input(_event: InputEvent, _action: String) -> bool:
- return false
-
-
-# This class is useful for the accelerators of PopupMenu items
-# It's possible for PopupMenu items to have multiple shortcuts by using
-# set_item_shortcut(), but we have no control over the accelerator text that appears.
-# Thus, we are stuck with using accelerators instead of shortcuts.
-# If Godot ever receives the ability to change the accelerator text of the items,
-# we could in theory remove this class.
-# If you don't care about PopupMenus in the same scene as ShortcutEdit
-# such as projects like Pixelorama where everything is in the same scene,
-# then you can ignore this class.
-class MenuInputAction:
- extends InputAction
- var node_path := ""
- var node: PopupMenu
- var menu_item_id := 0
- var echo := false
-
- func _init(
- _display_name := "",
- _group := "",
- _global := true,
- _node_path := "",
- _menu_item_id := 0,
- _echo := false
- ) -> void:
- super._init(_display_name, _group, _global)
- node_path = _node_path
- menu_item_id = _menu_item_id
- echo = _echo
-
- func get_node(root: Node) -> void:
- var temp_node = root.get_node(node_path)
- if temp_node is PopupMenu:
- node = node
- elif temp_node is MenuButton:
- node = temp_node.get_popup()
-
- func update_node(action: String) -> void:
- if !node:
- return
- var first_key: InputEventKey = Keychain.action_get_first_key(action)
- var accel := first_key.get_keycode_with_modifiers() if first_key else 0
- node.set_item_accelerator(menu_item_id, accel)
-
- func handle_input(event: InputEvent, action: String) -> bool:
- if not node:
- return false
- if event.is_action_pressed(action, false, true):
- if event is InputEventKey:
- var acc: int = node.get_item_accelerator(menu_item_id)
- # If the event is the same as the menu item's accelerator, skip
- if acc == event.get_keycode_with_modifiers():
- return true
- node.emit_signal("id_pressed", menu_item_id)
- return true
- if event.is_action(action, true) and echo:
- if event.is_echo():
- node.emit_signal("id_pressed", menu_item_id)
- return true
-
- return false
-
class InputGroup:
var parent_group := ""
@@ -115,13 +54,10 @@ func _ready() -> void:
if !config_path.is_empty():
config_file.load(config_path)
- set_process_input(multiple_menu_accelerators)
-
# Load shortcut profiles
DirAccess.make_dir_recursive_absolute(PROFILES_PATH)
var profile_dir := DirAccess.open(PROFILES_PATH)
- profile_dir.make_dir(PROFILES_PATH)
- profile_dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
+ profile_dir.list_dir_begin()
var file_name = profile_dir.get_next()
while file_name != "":
if !profile_dir.current_is_dir():
@@ -143,35 +79,9 @@ func _ready() -> void:
for profile in profiles:
profile.fill_bindings()
- var l18n_dir := DirAccess.open(TRANSLATIONS_PATH)
- l18n_dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
- file_name = l18n_dir.get_next()
- while file_name != "":
- if !l18n_dir.current_is_dir():
- if file_name.get_extension() == "po":
- var t: Translation = load(TRANSLATIONS_PATH.path_join(file_name))
- TranslationServer.add_translation(t)
- file_name = l18n_dir.get_next()
-
profile_index = config_file.get_value("shortcuts", "shortcuts_profile", 0)
change_profile(profile_index)
- for action in actions:
- var input_action: InputAction = actions[action]
- if input_action is MenuInputAction:
- input_action.get_node(get_tree().current_scene)
-
-
-func _input(event: InputEvent) -> void:
- if event is InputEventMouseMotion:
- return
-
- for action in actions:
- var input_action: InputAction = actions[action]
- var done: bool = input_action.handle_input(event, action)
- if done:
- return
-
func change_profile(index: int) -> void:
if index >= profiles.size():
@@ -184,29 +94,35 @@ func change_profile(index: int) -> void:
action_add_event(action, event)
-func action_add_event(action: String, event: InputEvent) -> void:
+func action_add_event(action: StringName, event: InputEvent) -> void:
InputMap.action_add_event(action, event)
- if action in actions:
- actions[action].update_node(action)
-func action_erase_event(action: String, event: InputEvent) -> void:
+func action_erase_event(action: StringName, event: InputEvent) -> void:
InputMap.action_erase_event(action, event)
- if action in actions:
- actions[action].update_node(action)
-func action_erase_events(action: String) -> void:
+func action_erase_events(action: StringName) -> void:
InputMap.action_erase_events(action)
- if action in actions:
- actions[action].update_node(action)
-
-
-func action_get_first_key(action: String) -> InputEventKey:
- var first_key: InputEventKey = null
- var events := InputMap.action_get_events(action)
- for event in events:
- if event is InputEventKey:
- first_key = event
- break
- return first_key
+
+
+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
diff --git a/game/addons/keychain/ShortcutEdit.gd b/game/addons/keychain/ShortcutEdit.gd
index fe4e69f..207ddec 100644
--- a/game/addons/keychain/ShortcutEdit.gd
+++ b/game/addons/keychain/ShortcutEdit.gd
@@ -2,7 +2,7 @@ extends Control
enum { KEYBOARD, MOUSE, JOY_BUTTON, JOY_AXIS }
-const MOUSE_BUTTON_NAMES := [
+const MOUSE_BUTTON_NAMES: PackedStringArray = [
"Left Button",
"Right Button",
"Middle Button",
@@ -14,7 +14,7 @@ const MOUSE_BUTTON_NAMES := [
"X Button 2",
]
-const JOY_BUTTON_NAMES := [
+const JOY_BUTTON_NAMES: PackedStringArray = [
"DualShock Cross, Xbox A, Nintendo B",
"DualShock Circle, Xbox B, Nintendo A",
"DualShock Square, Xbox X, Nintendo Y",
@@ -40,7 +40,7 @@ const JOY_BUTTON_NAMES := [
"PS4/5 Touchpad",
]
-const JOY_AXIS_NAMES := [
+const JOY_AXIS_NAMES: PackedStringArray = [
"(Left Stick Left)",
"(Left Stick Right)",
"(Left Stick Up)",
@@ -107,7 +107,7 @@ func _ready() -> void:
profile_option_button.select(Keychain.profile_index)
_on_ProfileOptionButton_item_selected(Keychain.profile_index)
- if OS.get_name() == "HTML5":
+ if OS.get_name() == "Web":
$VBoxContainer/HBoxContainer/OpenProfileFolder.queue_free()
@@ -151,8 +151,6 @@ func _construct_tree() -> void:
func _fill_selector_options() -> void:
- keyboard_shortcut_selector.entered_shortcut.visible = true
- keyboard_shortcut_selector.option_button.visible = false
mouse_shortcut_selector.input_type_l.text = "Mouse Button Index:"
joy_key_shortcut_selector.input_type_l.text = "Joypad Button Index:"
joy_axis_shortcut_selector.input_type_l.text = "Joypad Axis Index:"
@@ -171,8 +169,8 @@ func _fill_selector_options() -> void:
var joy_axis_option_button: OptionButton = joy_axis_shortcut_selector.option_button
var i := 0.0
for option in JOY_AXIS_NAMES:
- var sign_symbol = "+" if floor(i) != i else "-"
- var text: String = tr("Axis") + " %s %s %s" % [floor(i), sign_symbol, tr(option)]
+ var sign_symbol := "+" if floori(i) != i else "-"
+ var text: String = tr("Axis") + " %s %s %s" % [floori(i), sign_symbol, tr(option)]
joy_axis_option_button.add_item(text)
i += 0.5
@@ -201,19 +199,10 @@ func get_action_name(action: String) -> String:
display_name = Keychain.actions[action].display_name
if display_name.is_empty():
- display_name = _humanize_snake_case(action)
+ display_name = Keychain.humanize_snake_case(action)
return display_name
-func _humanize_snake_case(text: String) -> String:
- text = text.replace("_", " ")
- var first_letter := text.left(1)
- first_letter = first_letter.capitalize()
- text = text.right(-1)
- text = text.insert(0, first_letter)
- return text
-
-
func add_event_tree_item(event: InputEvent, action_tree_item: TreeItem) -> void:
var event_class := event.get_class()
match event_class:
@@ -252,21 +241,10 @@ func add_event_tree_item(event: InputEvent, action_tree_item: TreeItem) -> void:
func event_to_str(event: InputEvent) -> String:
- var output := ""
- if event is InputEventKey:
- var scancode: int = 0
- if event.keycode != 0:
- scancode = event.get_keycode_with_modifiers()
- var physical_str := ""
- if scancode == 0:
- scancode = event.get_physical_keycode_with_modifiers()
- physical_str = " " + tr("(Physical)")
- output = OS.get_keycode_string(scancode) + physical_str
-
- elif event is InputEventMouseButton:
- output = tr(MOUSE_BUTTON_NAMES[event.button_index - 1])
-
- elif event is InputEventJoypadButton:
+ var output := event.as_text()
+ # event.as_text() could be used for these event types as well, but this gives more control
+ # to the developer as to what strings will be printed
+ if event is InputEventJoypadButton:
var button_index: int = event.button_index
output = tr("Button")
if button_index >= JOY_BUTTON_NAMES.size():
diff --git a/game/addons/keychain/ShortcutEdit.tscn b/game/addons/keychain/ShortcutEdit.tscn
index daeb0b8..57bfd46 100644
--- a/game/addons/keychain/ShortcutEdit.tscn
+++ b/game/addons/keychain/ShortcutEdit.tscn
@@ -7,7 +7,8 @@
[ext_resource type="Texture2D" uid="uid://bma7xj2rqqcr8" path="res://addons/keychain/assets/mouse.svg" id="5"]
[ext_resource type="PackedScene" uid="uid://bfjcafe2kvx7n" path="res://addons/keychain/ShortcutSelectorDialog.tscn" id="6"]
-[node name="ShortcutEdit" type="VBoxContainer"]
+[node name="ShortcutEdit" type="Control"]
+layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
@@ -17,7 +18,12 @@ size_flags_vertical = 3
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
-layout_mode = 2
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
@@ -94,8 +100,6 @@ offset_left = 8.0
offset_top = 8.0
offset_right = 192.0
offset_bottom = 51.0
-caret_blink = true
-caret_blink_interval = 0.5
[node name="DeleteConfirmation" type="ConfirmationDialog" parent="."]
size = Vector2i(427, 100)
diff --git a/game/addons/keychain/ShortcutSelectorDialog.gd b/game/addons/keychain/ShortcutSelectorDialog.gd
index 38cb555..fb28434 100644
--- a/game/addons/keychain/ShortcutSelectorDialog.gd
+++ b/game/addons/keychain/ShortcutSelectorDialog.gd
@@ -2,27 +2,38 @@ extends ConfirmationDialog
enum InputTypes { KEYBOARD, MOUSE, JOY_BUTTON, JOY_AXIS }
-@export var input_type: InputTypes = InputTypes.KEYBOARD
+@export var input_type := 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
+@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
@@ -31,7 +42,8 @@ func _input(event: InputEvent) -> void:
return
if event.pressed:
listened_input = event
- entered_shortcut.text = OS.get_keycode_string(event.get_keycode_with_modifiers())
+ _set_modifier_buttons_state(listened_input)
+ entered_shortcut.text = event.as_text()
_show_assigned_state(event)
@@ -101,27 +113,13 @@ func _set_shortcut(action: StringName, old_event: InputEvent, new_event: InputEv
tree_item.free()
break
- tree_item = _get_next_tree_item(tree_item)
+ tree_item = tree_item.get_next_in_tree()
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:
@@ -148,23 +146,26 @@ func _find_matching_event_in_map(action: StringName, event: InputEvent) -> Array
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:
- 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
+ _set_modifier_buttons_state(metadata)
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
+ index += signi(metadata.axis_value) / 2.0 + 0.5
option_button.select(index)
_on_OptionButton_item_selected(index)
@@ -181,6 +182,11 @@ 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
@@ -197,3 +203,50 @@ func _on_EnteredShortcut_focus_entered() -> void:
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()
diff --git a/game/addons/keychain/ShortcutSelectorDialog.tscn b/game/addons/keychain/ShortcutSelectorDialog.tscn
index d8f528f..65250e3 100644
--- a/game/addons/keychain/ShortcutSelectorDialog.tscn
+++ b/game/addons/keychain/ShortcutSelectorDialog.tscn
@@ -3,44 +3,67 @@
[ext_resource type="Script" path="res://addons/keychain/ShortcutSelectorDialog.gd" id="1"]
[node name="ShortcutSelectorDialog" type="ConfirmationDialog"]
+size = Vector2i(417, 169)
script = ExtResource("1")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
offset_left = 8.0
offset_top = 8.0
-offset_right = 341.0
-offset_bottom = 64.0
+offset_right = 409.0
+offset_bottom = 120.0
[node name="InputTypeLabel" type="Label" parent="VBoxContainer"]
layout_mode = 2
-offset_right = 333.0
-offset_bottom = 14.0
text = "Press a key or a key combination to set the shortcut"
[node name="EnteredShortcut" type="LineEdit" parent="VBoxContainer"]
visible = false
layout_mode = 2
-offset_top = 18.0
-offset_right = 333.0
-offset_bottom = 32.0
editable = false
virtual_keyboard_enabled = false
[node name="OptionButton" type="OptionButton" parent="VBoxContainer"]
layout_mode = 2
-offset_top = 18.0
-offset_right = 333.0
-offset_bottom = 38.0
mouse_default_cursor_shape = 2
+[node name="ModifierButtons" type="HBoxContainer" parent="VBoxContainer"]
+layout_mode = 2
+
+[node name="Alt" type="CheckBox" parent="VBoxContainer/ModifierButtons"]
+layout_mode = 2
+mouse_default_cursor_shape = 2
+text = "Alt"
+
+[node name="Shift" type="CheckBox" parent="VBoxContainer/ModifierButtons"]
+layout_mode = 2
+mouse_default_cursor_shape = 2
+text = "Shift"
+
+[node name="Control" type="CheckBox" parent="VBoxContainer/ModifierButtons"]
+layout_mode = 2
+mouse_default_cursor_shape = 2
+text = "Control"
+
+[node name="Meta" type="CheckBox" parent="VBoxContainer/ModifierButtons"]
+layout_mode = 2
+mouse_default_cursor_shape = 2
+text = "Meta"
+
+[node name="CommandOrControl" type="CheckBox" parent="VBoxContainer/ModifierButtons"]
+layout_mode = 2
+mouse_default_cursor_shape = 2
+text = "Command / Control (auto)"
+
[node name="AlreadyExistsLabel" type="Label" parent="VBoxContainer"]
layout_mode = 2
-offset_top = 42.0
-offset_right = 333.0
-offset_bottom = 56.0
[connection signal="about_to_popup" from="." to="." method="_on_ShortcutSelectorDialog_about_to_show"]
[connection signal="confirmed" from="." to="." method="_on_ShortcutSelectorDialog_confirmed"]
[connection signal="focus_entered" from="VBoxContainer/EnteredShortcut" to="." method="_on_EnteredShortcut_focus_entered"]
[connection signal="focus_exited" from="VBoxContainer/EnteredShortcut" to="." method="_on_EnteredShortcut_focus_exited"]
[connection signal="item_selected" from="VBoxContainer/OptionButton" to="." method="_on_OptionButton_item_selected"]
+[connection signal="toggled" from="VBoxContainer/ModifierButtons/Alt" to="." method="_on_alt_toggled"]
+[connection signal="toggled" from="VBoxContainer/ModifierButtons/Shift" to="." method="_on_shift_toggled"]
+[connection signal="toggled" from="VBoxContainer/ModifierButtons/Control" to="." method="_on_control_toggled"]
+[connection signal="toggled" from="VBoxContainer/ModifierButtons/Meta" to="." method="_on_meta_toggled"]
+[connection signal="toggled" from="VBoxContainer/ModifierButtons/CommandOrControl" to="." method="_on_command_or_control_toggled"]