summaryrefslogtreecommitdiff
path: root/core/input
diff options
context:
space:
mode:
Diffstat (limited to 'core/input')
-rw-r--r--core/input/input.cpp20
-rw-r--r--core/input/input_event.cpp9
-rw-r--r--core/input/input_event.h3
-rw-r--r--core/input/input_map.cpp80
-rw-r--r--core/input/input_map.h3
-rw-r--r--core/input/shortcut.cpp76
-rw-r--r--core/input/shortcut.h54
7 files changed, 187 insertions, 58 deletions
diff --git a/core/input/input.cpp b/core/input/input.cpp
index 72563cc40a..9195f7d8b5 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -240,18 +240,12 @@ bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const {
}
bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const {
-#ifdef DEBUG_ENABLED
- bool has_action = InputMap::get_singleton()->has_action(p_action);
- ERR_FAIL_COND_V_MSG(!has_action, false, "Request for nonexistent InputMap action '" + String(p_action) + "'.");
-#endif
+ ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
return action_state.has(p_action) && action_state[p_action].pressed && (p_exact ? action_state[p_action].exact : true);
}
bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const {
-#ifdef DEBUG_ENABLED
- bool has_action = InputMap::get_singleton()->has_action(p_action);
- ERR_FAIL_COND_V_MSG(!has_action, false, "Request for nonexistent InputMap action '" + String(p_action) + "'.");
-#endif
+ ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
const Map<StringName, Action>::Element *E = action_state.find(p_action);
if (!E) {
return false;
@@ -269,10 +263,7 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con
}
bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const {
-#ifdef DEBUG_ENABLED
- bool has_action = InputMap::get_singleton()->has_action(p_action);
- ERR_FAIL_COND_V_MSG(!has_action, false, "Request for nonexistent InputMap action '" + String(p_action) + "'.");
-#endif
+ ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action));
const Map<StringName, Action>::Element *E = action_state.find(p_action);
if (!E) {
return false;
@@ -290,10 +281,7 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co
}
float Input::get_action_strength(const StringName &p_action, bool p_exact) const {
-#ifdef DEBUG_ENABLED
- bool has_action = InputMap::get_singleton()->has_action(p_action);
- ERR_FAIL_COND_V_MSG(!has_action, false, "Request for nonexistent InputMap action '" + String(p_action) + "'.");
-#endif
+ ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action));
const Map<StringName, Action>::Element *E = action_state.find(p_action);
if (!E) {
return 0.0f;
diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp
index 325cdf2127..50b2099236 100644
--- a/core/input/input_event.cpp
+++ b/core/input/input_event.cpp
@@ -31,8 +31,8 @@
#include "input_event.h"
#include "core/input/input_map.h"
+#include "core/input/shortcut.h"
#include "core/os/keyboard.h"
-#include "scene/gui/shortcut.h"
const int InputEvent::DEVICE_ID_TOUCH_MOUSE = -1;
const int InputEvent::DEVICE_ID_INTERNAL = -2;
@@ -1545,6 +1545,13 @@ Ref<Shortcut> InputEventShortcut::get_shortcut() {
return shortcut;
}
+void InputEventShortcut::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_shortcut", "shortcut"), &InputEventShortcut::set_shortcut);
+ ClassDB::bind_method(D_METHOD("get_shortcut"), &InputEventShortcut::get_shortcut);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut");
+}
+
bool InputEventShortcut::is_pressed() const {
return true;
}
diff --git a/core/input/input_event.h b/core/input/input_event.h
index 517d63eb40..3fc8078a09 100644
--- a/core/input/input_event.h
+++ b/core/input/input_event.h
@@ -548,6 +548,9 @@ class InputEventShortcut : public InputEvent {
Ref<Shortcut> shortcut;
+protected:
+ static void _bind_methods();
+
public:
void set_shortcut(Ref<Shortcut> p_shortcut);
Ref<Shortcut> get_shortcut();
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 83ec70757e..fe4ee99204 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -59,7 +59,7 @@ void InputMap::_bind_methods() {
* Returns an nonexistent action error message with a suggestion of the closest
* matching action name (if possible).
*/
-String InputMap::_suggest_actions(const StringName &p_action) const {
+String InputMap::suggest_actions(const StringName &p_action) const {
List<StringName> actions = get_actions();
StringName closest_action;
float closest_similarity = 0.0;
@@ -93,7 +93,7 @@ void InputMap::add_action(const StringName &p_action, float p_deadzone) {
}
void InputMap::erase_action(const StringName &p_action) {
- ERR_FAIL_COND_MSG(!input_map.has(p_action), _suggest_actions(p_action));
+ ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action));
input_map.erase(p_action);
}
@@ -147,20 +147,20 @@ bool InputMap::has_action(const StringName &p_action) const {
}
float InputMap::action_get_deadzone(const StringName &p_action) {
- ERR_FAIL_COND_V_MSG(!input_map.has(p_action), 0.0f, _suggest_actions(p_action));
+ ERR_FAIL_COND_V_MSG(!input_map.has(p_action), 0.0f, suggest_actions(p_action));
return input_map[p_action].deadzone;
}
void InputMap::action_set_deadzone(const StringName &p_action, float p_deadzone) {
- ERR_FAIL_COND_MSG(!input_map.has(p_action), _suggest_actions(p_action));
+ ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action));
input_map[p_action].deadzone = p_deadzone;
}
void InputMap::action_add_event(const StringName &p_action, const Ref<InputEvent> &p_event) {
ERR_FAIL_COND_MSG(p_event.is_null(), "It's not a reference to a valid InputEvent object.");
- ERR_FAIL_COND_MSG(!input_map.has(p_action), _suggest_actions(p_action));
+ ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action));
if (_find_event(input_map[p_action], p_event, true)) {
return; // Already added.
}
@@ -169,12 +169,12 @@ void InputMap::action_add_event(const StringName &p_action, const Ref<InputEvent
}
bool InputMap::action_has_event(const StringName &p_action, const Ref<InputEvent> &p_event) {
- ERR_FAIL_COND_V_MSG(!input_map.has(p_action), false, _suggest_actions(p_action));
+ ERR_FAIL_COND_V_MSG(!input_map.has(p_action), false, suggest_actions(p_action));
return (_find_event(input_map[p_action], p_event, true) != nullptr);
}
void InputMap::action_erase_event(const StringName &p_action, const Ref<InputEvent> &p_event) {
- ERR_FAIL_COND_MSG(!input_map.has(p_action), _suggest_actions(p_action));
+ ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action));
List<Ref<InputEvent>>::Element *E = _find_event(input_map[p_action], p_event, true);
if (E) {
@@ -186,7 +186,7 @@ void InputMap::action_erase_event(const StringName &p_action, const Ref<InputEve
}
void InputMap::action_erase_events(const StringName &p_action) {
- ERR_FAIL_COND_MSG(!input_map.has(p_action), _suggest_actions(p_action));
+ ERR_FAIL_COND_MSG(!input_map.has(p_action), suggest_actions(p_action));
input_map[p_action].inputs.clear();
}
@@ -218,7 +218,7 @@ bool InputMap::event_is_action(const Ref<InputEvent> &p_event, const StringName
bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match, bool *p_pressed, float *p_strength, float *p_raw_strength) const {
OrderedHashMap<StringName, Action>::Element E = input_map.find(p_action);
- ERR_FAIL_COND_V_MSG(!E, false, _suggest_actions(p_action));
+ ERR_FAIL_COND_V_MSG(!E, false, suggest_actions(p_action));
Ref<InputEventAction> input_event_action = p_event;
if (input_event_action.is_valid()) {
@@ -317,36 +317,36 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = {
{ "ui_text_dedent", TTRC("Dedent") },
{ "ui_text_backspace", TTRC("Backspace") },
{ "ui_text_backspace_word", TTRC("Backspace Word") },
- { "ui_text_backspace_word.OSX", TTRC("Backspace Word") },
+ { "ui_text_backspace_word.osx", TTRC("Backspace Word") },
{ "ui_text_backspace_all_to_left", TTRC("Backspace all to Left") },
- { "ui_text_backspace_all_to_left.OSX", TTRC("Backspace all to Left") },
+ { "ui_text_backspace_all_to_left.osx", TTRC("Backspace all to Left") },
{ "ui_text_delete", TTRC("Delete") },
{ "ui_text_delete_word", TTRC("Delete Word") },
- { "ui_text_delete_word.OSX", TTRC("Delete Word") },
+ { "ui_text_delete_word.osx", TTRC("Delete Word") },
{ "ui_text_delete_all_to_right", TTRC("Delete all to Right") },
- { "ui_text_delete_all_to_right.OSX", TTRC("Delete all to Right") },
+ { "ui_text_delete_all_to_right.osx", TTRC("Delete all to Right") },
{ "ui_text_caret_left", TTRC("Caret Left") },
{ "ui_text_caret_word_left", TTRC("Caret Word Left") },
- { "ui_text_caret_word_left.OSX", TTRC("Caret Word Left") },
+ { "ui_text_caret_word_left.osx", TTRC("Caret Word Left") },
{ "ui_text_caret_right", TTRC("Caret Right") },
{ "ui_text_caret_word_right", TTRC("Caret Word Right") },
- { "ui_text_caret_word_right.OSX", TTRC("Caret Word Right") },
+ { "ui_text_caret_word_right.osx", TTRC("Caret Word Right") },
{ "ui_text_caret_up", TTRC("Caret Up") },
{ "ui_text_caret_down", TTRC("Caret Down") },
{ "ui_text_caret_line_start", TTRC("Caret Line Start") },
- { "ui_text_caret_line_start.OSX", TTRC("Caret Line Start") },
+ { "ui_text_caret_line_start.osx", TTRC("Caret Line Start") },
{ "ui_text_caret_line_end", TTRC("Caret Line End") },
- { "ui_text_caret_line_end.OSX", TTRC("Caret Line End") },
+ { "ui_text_caret_line_end.osx", TTRC("Caret Line End") },
{ "ui_text_caret_page_up", TTRC("Caret Page Up") },
{ "ui_text_caret_page_down", TTRC("Caret Page Down") },
{ "ui_text_caret_document_start", TTRC("Caret Document Start") },
- { "ui_text_caret_document_start.OSX", TTRC("Caret Document Start") },
+ { "ui_text_caret_document_start.osx", TTRC("Caret Document Start") },
{ "ui_text_caret_document_end", TTRC("Caret Document End") },
- { "ui_text_caret_document_end.OSX", TTRC("Caret Document End") },
+ { "ui_text_caret_document_end.osx", TTRC("Caret Document End") },
{ "ui_text_scroll_up", TTRC("Scroll Up") },
- { "ui_text_scroll_up.OSX", TTRC("Scroll Up") },
+ { "ui_text_scroll_up.osx", TTRC("Scroll Up") },
{ "ui_text_scroll_down", TTRC("Scroll Down") },
- { "ui_text_scroll_down.OSX", TTRC("Scroll Down") },
+ { "ui_text_scroll_down.osx", TTRC("Scroll Down") },
{ "ui_text_select_all", TTRC("Select All") },
{ "ui_text_select_word_under_caret", TTRC("Select Word Under Caret") },
{ "ui_text_toggle_insert_mode", TTRC("Toggle Insert Mode") },
@@ -516,14 +516,14 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_BACKSPACE | KEY_MASK_ALT));
- default_builtin_cache.insert("ui_text_backspace_word.OSX", inputs);
+ default_builtin_cache.insert("ui_text_backspace_word.osx", inputs);
inputs = List<Ref<InputEvent>>();
default_builtin_cache.insert("ui_text_backspace_all_to_left", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_BACKSPACE | KEY_MASK_CMD));
- default_builtin_cache.insert("ui_text_backspace_all_to_left.OSX", inputs);
+ default_builtin_cache.insert("ui_text_backspace_all_to_left.osx", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_DELETE));
@@ -535,14 +535,14 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_DELETE | KEY_MASK_ALT));
- default_builtin_cache.insert("ui_text_delete_word.OSX", inputs);
+ default_builtin_cache.insert("ui_text_delete_word.osx", inputs);
inputs = List<Ref<InputEvent>>();
default_builtin_cache.insert("ui_text_delete_all_to_right", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_DELETE | KEY_MASK_CMD));
- default_builtin_cache.insert("ui_text_delete_all_to_right.OSX", inputs);
+ default_builtin_cache.insert("ui_text_delete_all_to_right.osx", inputs);
// Text Caret Movement Left/Right
@@ -556,7 +556,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_LEFT | KEY_MASK_ALT));
- default_builtin_cache.insert("ui_text_caret_word_left.OSX", inputs);
+ default_builtin_cache.insert("ui_text_caret_word_left.osx", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_RIGHT));
@@ -568,7 +568,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_RIGHT | KEY_MASK_ALT));
- default_builtin_cache.insert("ui_text_caret_word_right.OSX", inputs);
+ default_builtin_cache.insert("ui_text_caret_word_right.osx", inputs);
// Text Caret Movement Up/Down
@@ -589,7 +589,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_A | KEY_MASK_CTRL));
inputs.push_back(InputEventKey::create_reference(KEY_LEFT | KEY_MASK_CMD));
- default_builtin_cache.insert("ui_text_caret_line_start.OSX", inputs);
+ default_builtin_cache.insert("ui_text_caret_line_start.osx", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_END));
@@ -598,7 +598,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_E | KEY_MASK_CTRL));
inputs.push_back(InputEventKey::create_reference(KEY_RIGHT | KEY_MASK_CMD));
- default_builtin_cache.insert("ui_text_caret_line_end.OSX", inputs);
+ default_builtin_cache.insert("ui_text_caret_line_end.osx", inputs);
// Text Caret Movement Page Up/Down
@@ -618,7 +618,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_UP | KEY_MASK_CMD));
- default_builtin_cache.insert("ui_text_caret_document_start.OSX", inputs);
+ default_builtin_cache.insert("ui_text_caret_document_start.osx", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_END | KEY_MASK_CMD));
@@ -626,7 +626,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_DOWN | KEY_MASK_CMD));
- default_builtin_cache.insert("ui_text_caret_document_end.OSX", inputs);
+ default_builtin_cache.insert("ui_text_caret_document_end.osx", inputs);
// Text Scrolling
@@ -636,7 +636,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_UP | KEY_MASK_CMD | KEY_MASK_ALT));
- default_builtin_cache.insert("ui_text_scroll_up.OSX", inputs);
+ default_builtin_cache.insert("ui_text_scroll_up.osx", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_DOWN | KEY_MASK_CMD));
@@ -644,7 +644,7 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(KEY_DOWN | KEY_MASK_CMD | KEY_MASK_ALT));
- default_builtin_cache.insert("ui_text_scroll_down.OSX", inputs);
+ default_builtin_cache.insert("ui_text_scroll_down.osx", inputs);
// Text Misc
@@ -702,11 +702,11 @@ const OrderedHashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
void InputMap::load_default() {
OrderedHashMap<String, List<Ref<InputEvent>>> builtins = get_builtins();
- // List of Builtins which have an override for OSX.
+ // List of Builtins which have an override for macOS.
Vector<String> osx_builtins;
for (OrderedHashMap<String, List<Ref<InputEvent>>>::Element E = builtins.front(); E; E = E.next()) {
- if (String(E.key()).ends_with(".OSX")) {
- // Strip .OSX from name: some_input_name.OSX -> some_input_name
+ if (String(E.key()).ends_with(".osx")) {
+ // Strip .osx from name: some_input_name.osx -> some_input_name
osx_builtins.push_back(String(E.key()).split(".")[0]);
}
}
@@ -717,13 +717,13 @@ void InputMap::load_default() {
String override_for = fullname.split(".").size() > 1 ? fullname.split(".")[1] : "";
#ifdef APPLE_STYLE_KEYS
- if (osx_builtins.has(name) && override_for != "OSX") {
- // Name has osx builtin but this particular one is for non-osx systems - so skip.
+ if (osx_builtins.has(name) && override_for != "osx") {
+ // Name has `osx` builtin but this particular one is for non-macOS systems - so skip.
continue;
}
#else
- if (override_for == "OSX") {
- // Override for OSX - not needed on non-osx platforms.
+ if (override_for == "osx") {
+ // Override for macOS - not needed on non-macOS platforms.
continue;
}
#endif
diff --git a/core/input/input_map.h b/core/input/input_map.h
index 0e0567464a..a2d3952f94 100644
--- a/core/input/input_map.h
+++ b/core/input/input_map.h
@@ -61,7 +61,6 @@ private:
Array _action_get_events(const StringName &p_action);
Array _get_actions();
- String _suggest_actions(const StringName &p_action) const;
protected:
static void _bind_methods();
@@ -89,6 +88,8 @@ public:
void load_from_project_settings();
void load_default();
+ String suggest_actions(const StringName &p_action) const;
+
String get_builtin_display_name(const String &p_name) const;
// Use an Ordered Map so insertion order is preserved. We want the elements to be 'grouped' somewhat.
const OrderedHashMap<String, List<Ref<InputEvent>>> &get_builtins();
diff --git a/core/input/shortcut.cpp b/core/input/shortcut.cpp
new file mode 100644
index 0000000000..d0cb08724e
--- /dev/null
+++ b/core/input/shortcut.cpp
@@ -0,0 +1,76 @@
+/*************************************************************************/
+/* shortcut.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "shortcut.h"
+#include "core/os/keyboard.h"
+
+void Shortcut::set_event(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND_MSG(Object::cast_to<InputEventShortcut>(*p_event), "Cannot set a shortcut event to an instance of InputEventShortcut.");
+ event = p_event;
+ emit_changed();
+}
+
+Ref<InputEvent> Shortcut::get_event() const {
+ return event;
+}
+
+bool Shortcut::matches_event(const Ref<InputEvent> &p_event) const {
+ Ref<InputEventShortcut> ies = p_event;
+ if (ies.is_valid()) {
+ if (ies->get_shortcut().ptr() == this) {
+ return true;
+ }
+ }
+ return event.is_valid() && event->is_match(p_event, true);
+}
+
+String Shortcut::get_as_text() const {
+ if (event.is_valid()) {
+ return event->as_text();
+ } else {
+ return "None";
+ }
+}
+
+bool Shortcut::has_valid_event() const {
+ return event.is_valid();
+}
+
+void Shortcut::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_event", "event"), &Shortcut::set_event);
+ ClassDB::bind_method(D_METHOD("get_event"), &Shortcut::get_event);
+
+ ClassDB::bind_method(D_METHOD("has_valid_event"), &Shortcut::has_valid_event);
+
+ ClassDB::bind_method(D_METHOD("matches_event", "event"), &Shortcut::matches_event);
+ ClassDB::bind_method(D_METHOD("get_as_text"), &Shortcut::get_as_text);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"), "set_event", "get_event");
+}
diff --git a/core/input/shortcut.h b/core/input/shortcut.h
new file mode 100644
index 0000000000..249dd1971f
--- /dev/null
+++ b/core/input/shortcut.h
@@ -0,0 +1,54 @@
+/*************************************************************************/
+/* shortcut.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SHORTCUT_H
+#define SHORTCUT_H
+
+#include "core/input/input_event.h"
+#include "core/io/resource.h"
+
+class Shortcut : public Resource {
+ GDCLASS(Shortcut, Resource);
+
+ Ref<InputEvent> event;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void set_event(const Ref<InputEvent> &p_shortcut);
+ Ref<InputEvent> get_event() const;
+ bool matches_event(const Ref<InputEvent> &p_event) const;
+ bool has_valid_event() const;
+
+ String get_as_text() const;
+};
+
+#endif // SHORTCUT_H