summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/core_constants.cpp4
-rw-r--r--core/extension/gdextension.cpp15
-rw-r--r--core/extension/gdextension.h20
-rw-r--r--core/extension/gdextension_interface.cpp17
-rw-r--r--core/extension/gdextension_interface.h25
-rw-r--r--core/input/input_event.cpp44
-rw-r--r--core/input/input_event.h5
-rw-r--r--core/os/keyboard.h6
-rw-r--r--core/variant/binder_common.h1
-rw-r--r--core/variant/variant.h1
-rw-r--r--doc/classes/@GlobalScope.xml10
-rw-r--r--doc/classes/Bone2D.xml2
-rw-r--r--doc/classes/EditorInterface.xml9
-rw-r--r--doc/classes/EditorSettings.xml6
-rw-r--r--doc/classes/InputEventKey.xml9
-rw-r--r--doc/classes/Object.xml111
-rw-r--r--doc/classes/ProjectSettings.xml2
-rw-r--r--editor/doc_tools.cpp12
-rw-r--r--editor/doc_tools.h1
-rw-r--r--editor/editor_dock_manager.cpp19
-rw-r--r--editor/editor_help.cpp34
-rw-r--r--editor/editor_help.h6
-rw-r--r--editor/editor_interface.cpp5
-rw-r--r--editor/editor_interface.h1
-rw-r--r--editor/editor_node.cpp24
-rw-r--r--editor/editor_node.h3
-rw-r--r--editor/editor_settings.cpp2
-rw-r--r--editor/event_listener_line_edit.cpp6
-rw-r--r--editor/input_event_configuration_dialog.cpp46
-rw-r--r--editor/input_event_configuration_dialog.h4
-rw-r--r--editor/plugins/script_editor_plugin.cpp17
-rw-r--r--editor/plugins/shader_editor_plugin.cpp23
-rw-r--r--editor/register_editor_types.cpp4
-rw-r--r--editor/window_wrapper.cpp14
-rw-r--r--modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml2
-rw-r--r--modules/gltf/editor/editor_import_blend_runner.cpp8
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp155
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.h2
-rw-r--r--modules/gltf/register_types.cpp46
-rw-r--r--platform/android/android_input_handler.cpp1
-rw-r--r--platform/android/android_keys_utils.cpp9
-rw-r--r--platform/android/android_keys_utils.h20
-rw-r--r--platform/ios/display_server_ios.h2
-rw-r--r--platform/ios/display_server_ios.mm3
-rw-r--r--platform/ios/key_mapping_ios.h1
-rw-r--r--platform/ios/key_mapping_ios.mm20
-rw-r--r--platform/ios/keyboard_input_view.mm8
-rw-r--r--platform/ios/view_controller.mm10
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp8
-rw-r--r--platform/linuxbsd/x11/key_mapping_x11.cpp22
-rw-r--r--platform/linuxbsd/x11/key_mapping_x11.h2
-rw-r--r--platform/macos/display_server_macos.h1
-rw-r--r--platform/macos/display_server_macos.mm2
-rw-r--r--platform/macos/godot_content_view.mm3
-rw-r--r--platform/macos/key_mapping_macos.h1
-rw-r--r--platform/macos/key_mapping_macos.mm24
-rw-r--r--platform/web/display_server_web.cpp3
-rw-r--r--platform/web/display_server_web.h1
-rw-r--r--platform/web/dom_keys.inc21
-rw-r--r--platform/windows/display_server_windows.cpp45
-rw-r--r--platform/windows/key_mapping_windows.cpp23
-rw-r--r--platform/windows/key_mapping_windows.h1
-rw-r--r--tests/core/input/test_input_event_key.h56
63 files changed, 788 insertions, 220 deletions
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index 3b96fc20c6..aaabbabfd9 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -507,6 +507,10 @@ void register_global_constants() {
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, KPAD);
BIND_CORE_BITFIELD_CLASS_FLAG(KeyModifierMask, KEY_MASK, GROUP_SWITCH);
+ BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, UNSPECIFIED);
+ BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, LEFT);
+ BIND_CORE_ENUM_CLASS_CONSTANT(KeyLocation, KEY_LOCATION, RIGHT);
+
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, NONE);
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, LEFT);
BIND_CORE_ENUM_CLASS_CONSTANT(MouseButton, MOUSE_BUTTON, RIGHT);
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index ce01531b5c..2904e54b22 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -653,6 +653,8 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
if (!ext->is_reloading) {
self->extension_classes.erase(class_name);
}
+
+ GDExtensionEditorHelp::remove_class(class_name);
#else
self->extension_classes.erase(class_name);
#endif
@@ -1196,4 +1198,17 @@ void GDExtensionEditorPlugins::remove_extension_class(const StringName &p_class_
extension_classes.erase(p_class_name);
}
}
+
+GDExtensionEditorHelp::EditorHelpLoadXmlBufferFunc GDExtensionEditorHelp::editor_help_load_xml_buffer = nullptr;
+GDExtensionEditorHelp::EditorHelpRemoveClassFunc GDExtensionEditorHelp::editor_help_remove_class = nullptr;
+
+void GDExtensionEditorHelp::load_xml_buffer(const uint8_t *p_buffer, int p_size) {
+ ERR_FAIL_NULL(editor_help_load_xml_buffer);
+ editor_help_load_xml_buffer(p_buffer, p_size);
+}
+
+void GDExtensionEditorHelp::remove_class(const String &p_class) {
+ ERR_FAIL_NULL(editor_help_remove_class);
+ editor_help_remove_class(p_class);
+}
#endif // TOOLS_ENABLED
diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h
index 0b39581751..588efed017 100644
--- a/core/extension/gdextension.h
+++ b/core/extension/gdextension.h
@@ -197,6 +197,26 @@ public:
return extension_classes;
}
};
+
+class GDExtensionEditorHelp {
+protected:
+ friend class EditorHelp;
+
+ // Similarly to EditorNode above, we need to be able to ask EditorHelp to parse
+ // new documentation data. Note though that, differently from EditorHelp, this
+ // is initialized even _before_ it gets instantiated, as we need to rely on
+ // this method while initializing the engine.
+ typedef void (*EditorHelpLoadXmlBufferFunc)(const uint8_t *p_buffer, int p_size);
+ static EditorHelpLoadXmlBufferFunc editor_help_load_xml_buffer;
+
+ typedef void (*EditorHelpRemoveClassFunc)(const String &p_class);
+ static EditorHelpRemoveClassFunc editor_help_remove_class;
+
+public:
+ static void load_xml_buffer(const uint8_t *p_buffer, int p_size);
+ static void remove_class(const String &p_class);
+};
+
#endif // TOOLS_ENABLED
#endif // GDEXTENSION_H
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index e02e7aa701..88572f24f0 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -42,6 +42,8 @@
#include "core/variant/variant.h"
#include "core/version.h"
+#include <string.h>
+
class CallableCustomExtension : public CallableCustom {
void *userdata;
void *token;
@@ -1373,6 +1375,19 @@ static void gdextension_editor_remove_plugin(GDExtensionConstStringNamePtr p_cla
#endif
}
+static void gdextension_editor_help_load_xml_from_utf8_chars_and_len(const char *p_data, GDExtensionInt p_size) {
+#ifdef TOOLS_ENABLED
+ GDExtensionEditorHelp::load_xml_buffer((const uint8_t *)p_data, p_size);
+#endif
+}
+
+static void gdextension_editor_help_load_xml_from_utf8_chars(const char *p_data) {
+#ifdef TOOLS_ENABLED
+ size_t len = strlen(p_data);
+ gdextension_editor_help_load_xml_from_utf8_chars_and_len(p_data, len);
+#endif
+}
+
#define REGISTER_INTERFACE_FUNC(m_name) GDExtension::register_interface_function(#m_name, (GDExtensionInterfaceFunctionPtr)&gdextension_##m_name)
void gdextension_setup_interface() {
@@ -1516,6 +1531,8 @@ void gdextension_setup_interface() {
REGISTER_INTERFACE_FUNC(classdb_get_class_tag);
REGISTER_INTERFACE_FUNC(editor_add_plugin);
REGISTER_INTERFACE_FUNC(editor_remove_plugin);
+ REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars);
+ REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars_and_len);
}
#undef REGISTER_INTERFACE_FUNCTION
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index d58f0226d8..8fda11c651 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -2617,6 +2617,31 @@ typedef void (*GDExtensionInterfaceEditorAddPlugin)(GDExtensionConstStringNamePt
*/
typedef void (*GDExtensionInterfaceEditorRemovePlugin)(GDExtensionConstStringNamePtr p_class_name);
+/**
+ * @name editor_help_load_xml_from_utf8_chars
+ * @since 4.3
+ *
+ * Loads new XML-formatted documentation data in the editor.
+ *
+ * The provided pointer can be immediately freed once the function returns.
+ *
+ * @param p_data A pointer to an UTF-8 encoded C string (null terminated).
+ */
+typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8Chars)(const char *p_data);
+
+/**
+ * @name editor_help_load_xml_from_utf8_chars_and_len
+ * @since 4.3
+ *
+ * Loads new XML-formatted documentation data in the editor.
+ *
+ * The provided pointer can be immediately freed once the function returns.
+ *
+ * @param p_data A pointer to an UTF-8 encoded C string.
+ * @param p_size The number of bytes (not code units).
+ */
+typedef void (*GDExtensionsInterfaceEditorHelpLoadXmlFromUtf8CharsAndLen)(const char *p_data, GDExtensionInt p_size);
+
#ifdef __cplusplus
}
#endif
diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp
index e99dd04599..89ffcecf50 100644
--- a/core/input/input_event.cpp
+++ b/core/input/input_event.cpp
@@ -364,6 +364,15 @@ char32_t InputEventKey::get_unicode() const {
return unicode;
}
+void InputEventKey::set_location(KeyLocation p_key_location) {
+ location = p_key_location;
+ emit_changed();
+}
+
+KeyLocation InputEventKey::get_location() const {
+ return location;
+}
+
void InputEventKey::set_echo(bool p_enable) {
echo = p_enable;
emit_changed();
@@ -436,6 +445,23 @@ String InputEventKey::as_text_key_label() const {
return mods_text.is_empty() ? kc : mods_text + "+" + kc;
}
+String InputEventKey::as_text_location() const {
+ String loc;
+
+ switch (location) {
+ case KeyLocation::LEFT:
+ loc = "left";
+ break;
+ case KeyLocation::RIGHT:
+ loc = "right";
+ break;
+ default:
+ break;
+ }
+
+ return loc;
+}
+
String InputEventKey::as_text() const {
String kc;
@@ -464,6 +490,11 @@ String InputEventKey::to_string() {
String kc = "";
String physical = "false";
+ String loc = as_text_location();
+ if (loc.is_empty()) {
+ loc = "unspecified";
+ }
+
if (keycode == Key::NONE && physical_keycode == Key::NONE && unicode != 0) {
kc = "U+" + String::num_uint64(unicode, 16) + " (" + String::chr(unicode) + ")";
} else if (keycode != Key::NONE) {
@@ -478,7 +509,7 @@ String InputEventKey::to_string() {
String mods = InputEventWithModifiers::as_text();
mods = mods.is_empty() ? "none" : mods;
- return vformat("InputEventKey: keycode=%s, mods=%s, physical=%s, pressed=%s, echo=%s", kc, mods, physical, p, e);
+ return vformat("InputEventKey: keycode=%s, mods=%s, physical=%s, location=%s, pressed=%s, echo=%s", kc, mods, physical, loc, p, e);
}
Ref<InputEventKey> InputEventKey::create_reference(Key p_keycode, bool p_physical) {
@@ -531,6 +562,9 @@ bool InputEventKey::action_match(const Ref<InputEvent> &p_event, bool p_exact_ma
match = keycode == key->keycode;
} else if (physical_keycode != Key::NONE) {
match = physical_keycode == key->physical_keycode;
+ if (location != KeyLocation::UNSPECIFIED) {
+ match &= location == key->location;
+ }
} else {
match = false;
}
@@ -572,6 +606,9 @@ bool InputEventKey::is_match(const Ref<InputEvent> &p_event, bool p_exact_match)
return (keycode == key->keycode) &&
(!p_exact_match || get_modifiers_mask() == key->get_modifiers_mask());
} else if (physical_keycode != Key::NONE) {
+ if (location != KeyLocation::UNSPECIFIED && location != key->location) {
+ return false;
+ }
return (physical_keycode == key->physical_keycode) &&
(!p_exact_match || get_modifiers_mask() == key->get_modifiers_mask());
} else {
@@ -594,6 +631,9 @@ void InputEventKey::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_unicode", "unicode"), &InputEventKey::set_unicode);
ClassDB::bind_method(D_METHOD("get_unicode"), &InputEventKey::get_unicode);
+ ClassDB::bind_method(D_METHOD("set_location", "location"), &InputEventKey::set_location);
+ ClassDB::bind_method(D_METHOD("get_location"), &InputEventKey::get_location);
+
ClassDB::bind_method(D_METHOD("set_echo", "echo"), &InputEventKey::set_echo);
ClassDB::bind_method(D_METHOD("get_keycode_with_modifiers"), &InputEventKey::get_keycode_with_modifiers);
@@ -603,12 +643,14 @@ void InputEventKey::_bind_methods() {
ClassDB::bind_method(D_METHOD("as_text_keycode"), &InputEventKey::as_text_keycode);
ClassDB::bind_method(D_METHOD("as_text_physical_keycode"), &InputEventKey::as_text_physical_keycode);
ClassDB::bind_method(D_METHOD("as_text_key_label"), &InputEventKey::as_text_key_label);
+ ClassDB::bind_method(D_METHOD("as_text_location"), &InputEventKey::as_text_location);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "keycode"), "set_keycode", "get_keycode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "physical_keycode"), "set_physical_keycode", "get_physical_keycode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "key_label"), "set_key_label", "get_key_label");
ADD_PROPERTY(PropertyInfo(Variant::INT, "unicode"), "set_unicode", "get_unicode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "location", PROPERTY_HINT_ENUM, "Unspecified,Left,Right"), "set_location", "get_location");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "echo"), "set_echo", "is_echo");
}
diff --git a/core/input/input_event.h b/core/input/input_event.h
index ed7ccf0a9f..61a53116e9 100644
--- a/core/input/input_event.h
+++ b/core/input/input_event.h
@@ -157,6 +157,7 @@ class InputEventKey : public InputEventWithModifiers {
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
uint32_t unicode = 0; ///unicode
+ KeyLocation location = KeyLocation::UNSPECIFIED;
bool echo = false; /// true if this is an echo key
@@ -178,6 +179,9 @@ public:
void set_unicode(char32_t p_unicode);
char32_t get_unicode() const;
+ void set_location(KeyLocation p_key_location);
+ KeyLocation get_location() const;
+
void set_echo(bool p_enable);
virtual bool is_echo() const override;
@@ -193,6 +197,7 @@ public:
virtual String as_text_physical_keycode() const;
virtual String as_text_keycode() const;
virtual String as_text_key_label() const;
+ virtual String as_text_location() const;
virtual String as_text() const override;
virtual String to_string() override;
diff --git a/core/os/keyboard.h b/core/os/keyboard.h
index 785972d31d..2051973336 100644
--- a/core/os/keyboard.h
+++ b/core/os/keyboard.h
@@ -260,6 +260,12 @@ enum class KeyModifierMask {
GROUP_SWITCH = (1 << 30)
};
+enum class KeyLocation {
+ UNSPECIFIED,
+ LEFT,
+ RIGHT
+};
+
// To avoid having unnecessary operators, only define the ones that are needed.
constexpr Key operator-(uint32_t a, Key b) {
diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h
index 34b54f1d00..c9f5ae7fc6 100644
--- a/core/variant/binder_common.h
+++ b/core/variant/binder_common.h
@@ -176,6 +176,7 @@ VARIANT_ENUM_CAST(Variant::Operator);
VARIANT_ENUM_CAST(Key);
VARIANT_BITFIELD_CAST(KeyModifierMask);
+VARIANT_ENUM_CAST(KeyLocation);
static inline Key &operator|=(Key &a, BitField<KeyModifierMask> b) {
a = static_cast<Key>(static_cast<int>(a) | static_cast<int>(b.operator int64_t()));
diff --git a/core/variant/variant.h b/core/variant/variant.h
index 602d287f22..429b346896 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -502,6 +502,7 @@ public:
VARIANT_ENUM_CLASS_CONSTRUCTOR(JoyAxis)
VARIANT_ENUM_CLASS_CONSTRUCTOR(JoyButton)
VARIANT_ENUM_CLASS_CONSTRUCTOR(Key)
+ VARIANT_ENUM_CLASS_CONSTRUCTOR(KeyLocation)
VARIANT_ENUM_CLASS_CONSTRUCTOR(MIDIMessage)
VARIANT_ENUM_CLASS_CONSTRUCTOR(MouseButton)
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index b74ca4b035..a2de6d2ac5 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -2374,6 +2374,16 @@
<constant name="KEY_MASK_GROUP_SWITCH" value="1073741824" enum="KeyModifierMask" is_bitfield="true">
Group Switch key mask.
</constant>
+ <constant name="KEY_LOCATION_UNSPECIFIED" value="0" enum="KeyLocation">
+ Used for keys which only appear once, or when a comparison doesn't need to differentiate the [code]LEFT[/code] and [code]RIGHT[/code] versions.
+ For example, when using [method InputEvent.is_match], an event which has [constant KEY_LOCATION_UNSPECIFIED] will match any [enum KeyLocation] on the passed event.
+ </constant>
+ <constant name="KEY_LOCATION_LEFT" value="1" enum="KeyLocation">
+ A key which is to the left of its twin.
+ </constant>
+ <constant name="KEY_LOCATION_RIGHT" value="2" enum="KeyLocation">
+ A key which is to the right of its twin.
+ </constant>
<constant name="MOUSE_BUTTON_NONE" value="0" enum="MouseButton">
Enum value which doesn't correspond to any mouse button. This is used to initialize [enum MouseButton] properties with a generic state.
</constant>
diff --git a/doc/classes/Bone2D.xml b/doc/classes/Bone2D.xml
index a62c160080..da7f08e53d 100644
--- a/doc/classes/Bone2D.xml
+++ b/doc/classes/Bone2D.xml
@@ -15,7 +15,7 @@
<method name="apply_rest">
<return type="void" />
<description>
- Stores the node's current transforms in [member rest].
+ Resets the bone to the rest pose. This is equivalent to setting [member Node2D.transform] to [member rest].
</description>
</method>
<method name="get_autocalculate_length_and_angle" qualifiers="const">
diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml
index d0de09e451..fc28622fdb 100644
--- a/doc/classes/EditorInterface.xml
+++ b/doc/classes/EditorInterface.xml
@@ -195,6 +195,15 @@
Shows the given property on the given [param object] in the editor's Inspector dock. If [param inspector_only] is [code]true[/code], plugins will not attempt to edit [param object].
</description>
</method>
+ <method name="is_multi_window_enabled" qualifiers="const">
+ <return type="bool" />
+ <description>
+ Returns [code]true[/code] if multiple window support is enabled in the editor. Multiple window support is enabled if [i]all[/i] of these statements are true:
+ - [member EditorSettings.interface/multi_window/enable] is [code]true[/code].
+ - [member EditorSettings.interface/editor/single_window_mode] is [code]false[/code].
+ - [member Viewport.gui_embed_subwindows] is [code]false[/code]. This is forced to [code]true[/code] on platforms that don't support multiple windows such as Web, or when the [code]--single-window[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url] is used.
+ </description>
+ </method>
<method name="is_playing_scene" qualifiers="const">
<return type="bool" />
<description>
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index b7f3ec9963..2f24acd684 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -495,7 +495,7 @@
<member name="filesystem/file_dialog/thumbnail_size" type="int" setter="" getter="">
The thumbnail size to use in the editor's file dialogs (in pixels). See also [member docks/filesystem/thumbnail_size].
</member>
- <member name="filesystem/import/blender/blender3_path" type="String" setter="" getter="">
+ <member name="filesystem/import/blender/blender_path" type="String" setter="" getter="">
The path to the directory containing the Blender executable used for converting the Blender 3D scene files [code].blend[/code] to glTF 2.0 format during import. Blender 3.0 or later is required.
To enable this feature for your specific project, use [member ProjectSettings.filesystem/import/blender/enabled].
</member>
@@ -616,6 +616,7 @@
</member>
<member name="interface/editor/single_window_mode" type="bool" setter="" getter="">
If [code]true[/code], embed modal windows such as docks inside the main editor window. When single-window mode is enabled, tooltips will also be embedded inside the main editor window, which means they can't be displayed outside of the editor window.
+ [b]Note:[/b] To query whether the editor can use multiple windows in an editor plugin, use [method EditorInterface.is_multi_window_enabled] instead of querying the value of this editor setting.
</member>
<member name="interface/editor/ui_layout_direction" type="int" setter="" getter="">
Editor UI default layout direction.
@@ -637,8 +638,9 @@
If [code]true[/code], display OpenType features marked as [code]hidden[/code] by the font file in the [Font] editor.
</member>
<member name="interface/multi_window/enable" type="bool" setter="" getter="">
- If [code]true[/code], the multi window support in editor is enabled. The following panels can become dedicated windows (made floating): Docks, Script editor, and Shader editor.
+ If [code]true[/code], multiple window support in editor is enabled. The following panels can become dedicated windows (i.e. made floating): Docks, Script editor, and Shader editor.
[b]Note:[/b] When [member interface/editor/single_window_mode] is [code]true[/code], the multi window support is always disabled.
+ [b]Note:[/b] To query whether the editor can use multiple windows in an editor plugin, use [method EditorInterface.is_multi_window_enabled] instead of querying the value of this editor setting.
</member>
<member name="interface/multi_window/maximize_window" type="bool" setter="" getter="">
If [code]true[/code], when panels are made floating they will be maximized.
diff --git a/doc/classes/InputEventKey.xml b/doc/classes/InputEventKey.xml
index 48a6804290..7b6fc3f216 100644
--- a/doc/classes/InputEventKey.xml
+++ b/doc/classes/InputEventKey.xml
@@ -24,6 +24,12 @@
Returns a [String] representation of the event's [member keycode] and modifiers.
</description>
</method>
+ <method name="as_text_location" qualifiers="const">
+ <return type="String" />
+ <description>
+ Returns a [String] representation of the event's [member location]. This will be a blank string if the event is not specific to a location.
+ </description>
+ </method>
<method name="as_text_physical_keycode" qualifiers="const">
<return type="String" />
<description>
@@ -77,6 +83,9 @@
+-----+ +-----+
[/codeblock]
</member>
+ <member name="location" type="int" setter="set_location" getter="get_location" enum="KeyLocation" default="0">
+ Represents the location of a key which has both left and right versions, such as [kbd]Shift[/kbd] or [kbd]Alt[/kbd].
+ </member>
<member name="physical_keycode" type="int" setter="set_physical_keycode" getter="get_physical_keycode" enum="Key" default="0">
Represents the physical location of a key on the 101/102-key US QWERTY keyboard, which corresponds to one of the [enum Key] constants.
To get a human-readable representation of the [InputEventKey], use [method OS.get_keycode_string] in combination with [method DisplayServer.keyboard_get_keycode_from_physical]:
diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml
index e4f4d7682b..5fa43f868e 100644
--- a/doc/classes/Object.xml
+++ b/doc/classes/Object.xml
@@ -76,83 +76,112 @@
<method name="_get_property_list" qualifiers="virtual">
<return type="Dictionary[]" />
<description>
- Override this method to customize how script properties should be handled by the engine.
+ Override this method to provide a custom list of additional properties to handle by the engine.
Should return a property list, as an [Array] of dictionaries. The result is added to the array of [method get_property_list], and should be formatted in the same way. Each [Dictionary] must at least contain the [code]name[/code] and [code]type[/code] entries.
- The example below displays [code]hammer_type[/code] in the Inspector dock, only if [code]holding_hammer[/code] is [code]true[/code]:
+ You can use [method _property_can_revert] and [method _property_get_revert] to customize the default values of the properties added by this method.
+ The example below displays a list of numbers shown as words going from [code]ZERO[/code] to [code]FIVE[/code], with [code]number_count[/code] controlling the size of the list:
[codeblocks]
[gdscript]
@tool
- extends Node2D
+ extends Node
- @export var holding_hammer = false:
- set(value):
- holding_hammer = value
+ @export var number_count = 3:
+ set(nc):
+ number_count = nc
+ numbers.resize(number_count)
notify_property_list_changed()
- var hammer_type = 0
-
- func _get_property_list():
- # By default, `hammer_type` is not visible in the editor.
- var property_usage = PROPERTY_USAGE_NO_EDITOR
- if holding_hammer:
- property_usage = PROPERTY_USAGE_DEFAULT
+ var numbers = PackedInt32Array([0, 0, 0])
+ func _get_property_list():
var properties = []
- properties.append({
- "name": "hammer_type",
- "type": TYPE_INT,
- "usage": property_usage, # See above assignment.
- "hint": PROPERTY_HINT_ENUM,
- "hint_string": "Wooden,Iron,Golden,Enchanted"
- })
+
+ for i in range(number_count):
+ properties.append({
+ "name": "number_%d" % i,
+ "type": TYPE_INT,
+ "hint": PROPERTY_HINT_ENUM,
+ "hint_string": "ZERO,ONE,TWO,THREE,FOUR,FIVE",
+ })
return properties
+
+ func _get(property):
+ if property.begins_with("number_"):
+ var index = property.get_slice("_", 1).to_int()
+ return numbers[index]
+
+ func _set(property, value):
+ if property.begins_with("number_"):
+ var index = property.get_slice("_", 1).to_int()
+ numbers[index] = value
+ return true
+ return false
[/gdscript]
[csharp]
[Tool]
- public partial class MyNode2D : Node2D
+ public partial class MyNode : Node
{
- private bool _holdingHammer;
+ private int _numberCount;
[Export]
- public bool HoldingHammer
+ public int NumberCount
{
- get =&gt; _holdingHammer;
+ get =&gt; _numberCount;
set
{
- _holdingHammer = value;
+ _numberCount = value;
+ _numbers.Resize(_numberCount);
NotifyPropertyListChanged();
}
}
- public int HammerType { get; set; }
+ private List&lt;int&gt; _numbers = new();
public override Godot.Collections.Array&lt;Godot.Collections.Dictionary&gt; _GetPropertyList()
{
- // By default, `HammerType` is not visible in the editor.
- var propertyUsage = PropertyUsageFlags.NoEditor;
+ var properties = new Godot.Collections.Array&lt;Godot.Collections.Dictionary&gt;();
- if (HoldingHammer)
+ for (int i = 0; i &lt; _numberCount; i++)
{
- propertyUsage = PropertyUsageFlags.Default;
+ properties.Add(new Godot.Collections.Dictionary()
+ {
+ { "name", $"number_{i}" },
+ { "type", (int)Variant.Type.Int },
+ { "hint", (int)PropertyHint.Enum },
+ { "hint_string", "Zero,One,Two,Three,Four,Five" },
+ });
}
- var properties = new Godot.Collections.Array&lt;Godot.Collections.Dictionary&gt;();
- properties.Add(new Godot.Collections.Dictionary()
+ return properties;
+ }
+
+ public override Variant _Get(StringName property)
+ {
+ string propertyName = property.ToString();
+ if (propertyName.StartsWith("number_"))
{
- { "name", "HammerType" },
- { "type", (int)Variant.Type.Int },
- { "usage", (int)propertyUsage }, // See above assignment.
- { "hint", (int)PropertyHint.Enum },
- { "hint_string", "Wooden,Iron,Golden,Enchanted" }
- });
+ int index = int.Parse(propertyName.Substring("number_".Length));
+ return _numbers[index];
+ }
+ return default;
+ }
- return properties;
+ public override bool _Set(StringName property, Variant value)
+ {
+ string propertyName = property.ToString();
+ if (propertyName.StartsWith("number_"))
+ {
+ int index = int.Parse(propertyName.Substring("number_".Length));
+ numbers[index] = value.As&lt;int&gt;();
+ return true;
+ }
+ return false;
}
}
[/csharp]
[/codeblocks]
- [b]Note:[/b] This method is intended for advanced purposes. For most common use cases, the scripting languages offer easier ways to handle properties. See [annotation @GDScript.@export], [annotation @GDScript.@export_enum], [annotation @GDScript.@export_group], etc.
+ [b]Note:[/b] This method is intended for advanced purposes. For most common use cases, the scripting languages offer easier ways to handle properties. See [annotation @GDScript.@export], [annotation @GDScript.@export_enum], [annotation @GDScript.@export_group], etc. If you want to customize exported properties, use [method _validate_property].
[b]Note:[/b] If the object's script is not [annotation @GDScript.@tool], this method will not be called in the editor.
</description>
</method>
@@ -274,7 +303,7 @@
<return type="void" />
<param index="0" name="property" type="Dictionary" />
<description>
- Override this method to customize existing properties. Every property info goes through this method. The dictionary contents is the same as in [method _get_property_list].
+ Override this method to customize existing properties. Every property info goes through this method, except properties added with [method _get_property_list]. The dictionary contents is the same as in [method _get_property_list].
[codeblocks]
[gdscript]
@tool
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index cbd797273c..0876261a31 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -943,7 +943,7 @@
</member>
<member name="filesystem/import/blender/enabled" type="bool" setter="" getter="" default="true">
If [code]true[/code], Blender 3D scene files with the [code].blend[/code] extension will be imported by converting them to glTF 2.0.
- This requires configuring a path to a Blender executable in the editor settings at [code]filesystem/import/blender/blender3_path[/code]. Blender 3.0 or later is required.
+ This requires configuring a path to a Blender executable in the editor settings at [code]filesystem/import/blender/blender_path[/code]. Blender 3.0 or later is required.
</member>
<member name="filesystem/import/blender/enabled.android" type="bool" setter="" getter="" default="false">
Override for [member filesystem/import/blender/enabled] on Android where Blender can't easily be accessed from Godot.
diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp
index 44f6444a31..952c093eb6 100644
--- a/editor/doc_tools.cpp
+++ b/editor/doc_tools.cpp
@@ -1652,3 +1652,15 @@ Error DocTools::load_compressed(const uint8_t *p_data, int p_compressed_size, in
return OK;
}
+
+Error DocTools::load_xml(const uint8_t *p_data, int p_size) {
+ Ref<XMLParser> parser = memnew(XMLParser);
+ Error err = parser->_open_buffer(p_data, p_size);
+ if (err) {
+ return err;
+ }
+
+ _load(parser);
+
+ return OK;
+}
diff --git a/editor/doc_tools.h b/editor/doc_tools.h
index 7f29cc238a..a6910baf28 100644
--- a/editor/doc_tools.h
+++ b/editor/doc_tools.h
@@ -56,6 +56,7 @@ public:
Error _load(Ref<XMLParser> parser);
Error load_compressed(const uint8_t *p_data, int p_compressed_size, int p_uncompressed_size);
+ Error load_xml(const uint8_t *p_data, int p_size);
};
#endif // DOC_TOOLS_H
diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp
index dfe9504706..54789bdef1 100644
--- a/editor/editor_dock_manager.cpp
+++ b/editor/editor_dock_manager.cpp
@@ -702,15 +702,18 @@ EditorDockManager::EditorDockManager() {
dock_select->set_v_size_flags(Control::SIZE_EXPAND_FILL);
dock_vb->add_child(dock_select);
- if (!SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable")) {
- dock_float = memnew(Button);
- dock_float->set_text(TTR("Make Floating"));
- dock_float->set_focus_mode(Control::FOCUS_NONE);
- dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
- dock_float->connect("pressed", callable_mp(this, &EditorDockManager::_dock_make_selected_float));
-
- dock_vb->add_child(dock_float);
+ dock_float = memnew(Button);
+ dock_float->set_text(TTR("Make Floating"));
+ if (!EditorNode::get_singleton()->is_multi_window_enabled()) {
+ dock_float->set_disabled(true);
+ dock_float->set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text());
+ } else {
+ dock_float->set_tooltip_text(TTR("Make this dock floating."));
}
+ dock_float->set_focus_mode(Control::FOCUS_NONE);
+ dock_float->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ dock_float->connect("pressed", callable_mp(this, &EditorDockManager::_dock_make_selected_float));
+ dock_vb->add_child(dock_float);
dock_select_popup->reset_size();
}
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index f0f7f87711..33862a6792 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -31,6 +31,7 @@
#include "editor_help.h"
#include "core/core_constants.h"
+#include "core/extension/gdextension.h"
#include "core/input/input.h"
#include "core/object/script_language.h"
#include "core/os/keyboard.h"
@@ -83,6 +84,7 @@ const Vector<String> classes_with_csharp_differences = {
// TODO: this is sometimes used directly as doc->something, other times as EditorHelp::get_doc_data(), which is thread-safe.
// Might this be a problem?
DocTools *EditorHelp::doc = nullptr;
+DocTools *EditorHelp::ext_doc = nullptr;
static bool _attempt_doc_load(const String &p_class) {
// Docgen always happens in the outer-most class: it also generates docs for inner classes.
@@ -2369,6 +2371,28 @@ String EditorHelp::get_cache_full_path() {
return EditorPaths::get_singleton()->get_cache_dir().path_join("editor_doc_cache.res");
}
+void EditorHelp::load_xml_buffer(const uint8_t *p_buffer, int p_size) {
+ if (!ext_doc) {
+ ext_doc = memnew(DocTools);
+ }
+
+ ext_doc->load_xml(p_buffer, p_size);
+
+ if (doc) {
+ doc->load_xml(p_buffer, p_size);
+ }
+}
+
+void EditorHelp::remove_class(const String &p_class) {
+ if (ext_doc && ext_doc->has_doc(p_class)) {
+ ext_doc->remove_doc(p_class);
+ }
+
+ if (doc && doc->has_doc(p_class)) {
+ doc->remove_doc(p_class);
+ }
+}
+
void EditorHelp::_load_doc_thread(void *p_udata) {
Ref<Resource> cache_res = ResourceLoader::load(get_cache_full_path());
if (cache_res.is_valid() && cache_res->get_meta("version_hash", "") == doc_version_hash) {
@@ -2416,6 +2440,11 @@ void EditorHelp::_gen_doc_thread(void *p_udata) {
void EditorHelp::_gen_extensions_docs() {
doc->generate((DocTools::GENERATE_FLAG_SKIP_BASIC_TYPES | DocTools::GENERATE_FLAG_EXTENSION_CLASSES_ONLY));
+
+ // Append extra doc data, as it gets overridden by the generation step.
+ if (ext_doc) {
+ doc->merge_from(*ext_doc);
+ }
}
void EditorHelp::generate_doc(bool p_use_cache) {
@@ -2554,6 +2583,11 @@ void EditorHelp::_bind_methods() {
ADD_SIGNAL(MethodInfo("go_to_help"));
}
+void EditorHelp::init_gdext_pointers() {
+ GDExtensionEditorHelp::editor_help_load_xml_buffer = &EditorHelp::load_xml_buffer;
+ GDExtensionEditorHelp::editor_help_remove_class = &EditorHelp::remove_class;
+}
+
EditorHelp::EditorHelp() {
set_custom_minimum_size(Size2(150 * EDSCALE, 0));
diff --git a/editor/editor_help.h b/editor/editor_help.h
index ff440a679a..896f0adf43 100644
--- a/editor/editor_help.h
+++ b/editor/editor_help.h
@@ -113,6 +113,7 @@ class EditorHelp : public VBoxContainer {
RichTextLabel *class_desc = nullptr;
HSplitContainer *h_split = nullptr;
static DocTools *doc;
+ static DocTools *ext_doc;
ConfirmationDialog *search_dialog = nullptr;
LineEdit *search = nullptr;
@@ -209,6 +210,9 @@ public:
static void cleanup_doc();
static String get_cache_full_path();
+ static void load_xml_buffer(const uint8_t *p_buffer, int p_size);
+ static void remove_class(const String &p_class);
+
void go_to_help(const String &p_help);
void go_to_class(const String &p_class);
void update_doc();
@@ -228,6 +232,8 @@ public:
void update_toggle_scripts_button();
+ static void init_gdext_pointers();
+
EditorHelp();
~EditorHelp();
};
diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp
index bad28ff43d..dcaf7fbd00 100644
--- a/editor/editor_interface.cpp
+++ b/editor/editor_interface.cpp
@@ -235,6 +235,10 @@ bool EditorInterface::is_distraction_free_mode_enabled() const {
return EditorNode::get_singleton()->is_distraction_free_mode_enabled();
}
+bool EditorInterface::is_multi_window_enabled() const {
+ return EditorNode::get_singleton()->is_multi_window_enabled();
+}
+
float EditorInterface::get_editor_scale() const {
return EDSCALE;
}
@@ -445,6 +449,7 @@ void EditorInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_main_screen_editor", "name"), &EditorInterface::set_main_screen_editor);
ClassDB::bind_method(D_METHOD("set_distraction_free_mode", "enter"), &EditorInterface::set_distraction_free_mode);
ClassDB::bind_method(D_METHOD("is_distraction_free_mode_enabled"), &EditorInterface::is_distraction_free_mode_enabled);
+ ClassDB::bind_method(D_METHOD("is_multi_window_enabled"), &EditorInterface::is_multi_window_enabled);
ClassDB::bind_method(D_METHOD("get_editor_scale"), &EditorInterface::get_editor_scale);
diff --git a/editor/editor_interface.h b/editor/editor_interface.h
index 73e89ae2f2..9515a1226f 100644
--- a/editor/editor_interface.h
+++ b/editor/editor_interface.h
@@ -99,6 +99,7 @@ public:
void set_main_screen_editor(const String &p_name);
void set_distraction_free_mode(bool p_enter);
bool is_distraction_free_mode_enabled() const;
+ bool is_multi_window_enabled() const;
float get_editor_scale() const;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 24bfba3844..23d5704048 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -870,7 +870,11 @@ void EditorNode::_resources_changed(const Vector<String> &p_resources) {
}
if (!res->editor_can_reload_from_file()) {
- continue;
+ Ref<Script> scr = res;
+ // Scripts are reloaded via the script editor.
+ if (scr.is_null() || ScriptEditor::get_singleton()->get_open_scripts().has(scr)) {
+ continue;
+ }
}
if (!res->get_path().is_resource_file() && !res->get_path().is_absolute_path()) {
continue;
@@ -3711,6 +3715,10 @@ bool EditorNode::is_scene_open(const String &p_path) {
return false;
}
+bool EditorNode::is_multi_window_enabled() const {
+ return !SceneTree::get_singleton()->get_root()->is_embedding_subwindows() && !EDITOR_GET("interface/editor/single_window_mode") && EDITOR_GET("interface/multi_window/enable");
+}
+
void EditorNode::fix_dependencies(const String &p_for_file) {
dependency_fixer->edit(p_for_file);
}
@@ -4122,6 +4130,20 @@ void EditorNode::request_instantiate_scenes(const Vector<String> &p_files) {
SceneTreeDock::get_singleton()->instantiate_scenes(p_files);
}
+String EditorNode::get_multiwindow_support_tooltip_text() const {
+ if (SceneTree::get_singleton()->get_root()->is_embedding_subwindows()) {
+ if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_SUBWINDOWS)) {
+ return TTR("Multi-window support is not available because the `--single-window` command line argument was used to start the editor.");
+ } else {
+ return TTR("Multi-window support is not available because the current platform doesn't support multiple windows.");
+ }
+ } else if (EDITOR_GET("interface/editor/single_window_mode")) {
+ return TTR("Multi-window support is not available because Interface > Editor > Single Window Mode is enabled in the editor settings.");
+ }
+
+ return TTR("Multi-window support is not available because Interface > Multi Window > Enable is disabled in the editor settings.");
+}
+
void EditorNode::_inherit_request(String p_file) {
current_menu_option = FILE_NEW_INHERITED_SCENE;
_dialog_action(p_file);
diff --git a/editor/editor_node.h b/editor/editor_node.h
index f1dea0c11e..014b72c580 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -758,6 +758,8 @@ public:
bool is_resource_read_only(Ref<Resource> p_resource, bool p_foreign_resources_are_writable = false);
+ String get_multiwindow_support_tooltip_text() const;
+
bool is_changing_scene() const;
VBoxContainer *get_main_screen_control();
@@ -807,6 +809,7 @@ public:
List<AdditiveNodeEntry> &p_addition_list);
bool is_scene_open(const String &p_path);
+ bool is_multi_window_enabled() const;
void setup_color_picker(ColorPicker *p_picker);
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 25510122f4..3b1a69459d 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -520,7 +520,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/file_dialog/thumbnail_size", 64, "32,128,16")
// Import (for glft module)
- EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_DIR, "filesystem/import/blender/blender3_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
+ EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/blender/blender_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_port", 6011, "0,65535,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_server_uptime", 5, "0,300,1,or_greater,suffix:s", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/fbx/fbx2gltf_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)
diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp
index 2c46e1c20a..c29e83d624 100644
--- a/editor/event_listener_line_edit.cpp
+++ b/editor/event_listener_line_edit.cpp
@@ -79,7 +79,11 @@ String EventListenerLineEdit::get_event_text(const Ref<InputEvent> &p_event, boo
if (!text.is_empty()) {
text += " " + TTR("or") + " ";
}
- text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + TTR("Physical") + ")";
+ text += mods_text + keycode_get_string(key->get_physical_keycode()) + " (" + TTR("Physical");
+ if (key->get_location() != KeyLocation::UNSPECIFIED) {
+ text += " " + key->as_text_location();
+ }
+ text += ")";
}
if (key->get_key_label() != Key::NONE) {
if (!text.is_empty()) {
diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp
index 0f483fcaef..22673bec64 100644
--- a/editor/input_event_configuration_dialog.cpp
+++ b/editor/input_event_configuration_dialog.cpp
@@ -64,6 +64,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c
bool show_mods = false;
bool show_device = false;
bool show_key = false;
+ bool show_location = false;
if (mod.is_valid()) {
show_mods = true;
@@ -77,12 +78,17 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c
if (k.is_valid()) {
show_key = true;
- if (k->get_keycode() == Key::NONE && k->get_physical_keycode() == Key::NONE && k->get_key_label() != Key::NONE) {
+ Key phys_key = k->get_physical_keycode();
+ if (k->get_keycode() == Key::NONE && phys_key == Key::NONE && k->get_key_label() != Key::NONE) {
key_mode->select(KEYMODE_UNICODE);
} else if (k->get_keycode() != Key::NONE) {
key_mode->select(KEYMODE_KEYCODE);
- } else if (k->get_physical_keycode() != Key::NONE) {
+ } else if (phys_key != Key::NONE) {
key_mode->select(KEYMODE_PHY_KEYCODE);
+ if (phys_key == Key::SHIFT || phys_key == Key::CTRL || phys_key == Key::ALT || phys_key == Key::META) {
+ key_location->select((int)k->get_location());
+ show_location = true;
+ }
} else {
// Invalid key.
event = Ref<InputEvent>();
@@ -103,6 +109,7 @@ void InputEventConfigurationDialog::_set_event(const Ref<InputEvent> &p_event, c
mod_container->set_visible(show_mods);
device_container->set_visible(show_device);
key_mode->set_visible(show_key);
+ location_container->set_visible(show_location);
additional_options_container->show();
// Update mode selector based on original key event.
@@ -240,6 +247,9 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven
k->set_physical_keycode(Key::NONE);
k->set_keycode(Key::NONE);
}
+ if (key_location->get_selected_id() == (int)KeyLocation::UNSPECIFIED) {
+ k->set_location(KeyLocation::UNSPECIFIED);
+ }
}
Ref<InputEventWithModifiers> mod = received_event;
@@ -433,6 +443,17 @@ void InputEventConfigurationDialog::_key_mode_selected(int p_mode) {
_set_event(k, original_event);
}
+void InputEventConfigurationDialog::_key_location_selected(int p_location) {
+ Ref<InputEventKey> k = event;
+ if (k.is_null()) {
+ return;
+ }
+
+ k->set_location((KeyLocation)p_location);
+
+ _set_event(k, original_event);
+}
+
void InputEventConfigurationDialog::_input_list_item_selected() {
TreeItem *selected = input_list_tree->get_selected();
@@ -594,6 +615,8 @@ void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p
// Select "All Devices" by default.
device_id_option->select(0);
+ // Also "all locations".
+ key_location->select(0);
}
if (!p_current_action_name.is_empty()) {
@@ -726,5 +749,24 @@ InputEventConfigurationDialog::InputEventConfigurationDialog() {
key_mode->hide();
additional_options_container->add_child(key_mode);
+ // Key Location Selection
+
+ location_container = memnew(HBoxContainer);
+ location_container->hide();
+
+ Label *location_label = memnew(Label);
+ location_label->set_text(TTR("Physical location"));
+ location_container->add_child(location_label);
+
+ key_location = memnew(OptionButton);
+ key_location->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ key_location->add_item(TTR("Any"), (int)KeyLocation::UNSPECIFIED);
+ key_location->add_item(TTR("Left"), (int)KeyLocation::LEFT);
+ key_location->add_item(TTR("Right"), (int)KeyLocation::RIGHT);
+ key_location->connect("item_selected", callable_mp(this, &InputEventConfigurationDialog::_key_location_selected));
+
+ location_container->add_child(key_location);
+ additional_options_container->add_child(location_container);
+
main_vbox->add_child(additional_options_container);
}
diff --git a/editor/input_event_configuration_dialog.h b/editor/input_event_configuration_dialog.h
index bde0b73ade..3ef089be8b 100644
--- a/editor/input_event_configuration_dialog.h
+++ b/editor/input_event_configuration_dialog.h
@@ -99,6 +99,9 @@ private:
OptionButton *key_mode = nullptr;
+ HBoxContainer *location_container = nullptr;
+ OptionButton *key_location = nullptr;
+
void _set_event(const Ref<InputEvent> &p_event, const Ref<InputEvent> &p_original_event, bool p_update_input_list_selection = true);
void _on_listen_input_changed(const Ref<InputEvent> &p_event);
void _on_listen_focus_changed();
@@ -110,6 +113,7 @@ private:
void _mod_toggled(bool p_checked, int p_index);
void _autoremap_command_or_control_toggled(bool p_checked);
void _key_mode_selected(int p_mode);
+ void _key_location_selected(int p_location);
void _device_selection_changed(int p_option_button_index);
void _set_current_device(int p_device);
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 55191f44d4..c8e65e98a7 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -4074,18 +4074,19 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
script_forward->set_disabled(true);
script_forward->set_tooltip_text(TTR("Go to next edited document."));
- if (p_wrapper->is_window_available()) {
- menu_hb->add_child(memnew(VSeparator));
+ menu_hb->add_child(memnew(VSeparator));
- make_floating = memnew(ScreenSelect);
- make_floating->set_flat(true);
+ make_floating = memnew(ScreenSelect);
+ make_floating->set_flat(true);
+ make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));
+ if (!make_floating->is_disabled()) {
+ // Override default ScreenSelect tooltip if multi-window support is available.
make_floating->set_tooltip_text(TTR("Make the script editor floating."));
- make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));
-
- menu_hb->add_child(make_floating);
- p_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditor::_window_changed));
}
+ menu_hb->add_child(make_floating);
+ p_wrapper->connect("window_visibility_changed", callable_mp(this, &ScriptEditor::_window_changed));
+
tab_container->connect("tab_changed", callable_mp(this, &ScriptEditor::_tab_changed));
erase_tab_confirm = memnew(ConfirmationDialog);
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index 5798ff9d99..a018ec095b 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -643,20 +643,21 @@ ShaderEditorPlugin::ShaderEditorPlugin() {
file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(i), true);
}
- if (window_wrapper->is_window_available()) {
- Control *padding = memnew(Control);
- padding->set_h_size_flags(Control::SIZE_EXPAND_FILL);
- menu_hb->add_child(padding);
-
- make_floating = memnew(ScreenSelect);
- make_floating->set_flat(true);
+ Control *padding = memnew(Control);
+ padding->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ menu_hb->add_child(padding);
+
+ make_floating = memnew(ScreenSelect);
+ make_floating->set_flat(true);
+ make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));
+ if (!make_floating->is_disabled()) {
+ // Override default ScreenSelect tooltip if multi-window support is available.
make_floating->set_tooltip_text(TTR("Make the shader editor floating."));
- make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true));
-
- menu_hb->add_child(make_floating);
- window_wrapper->connect("window_visibility_changed", callable_mp(this, &ShaderEditorPlugin::_window_changed));
}
+ menu_hb->add_child(make_floating);
+ window_wrapper->connect("window_visibility_changed", callable_mp(this, &ShaderEditorPlugin::_window_changed));
+
shader_list = memnew(ItemList);
shader_list->set_auto_translate(false);
shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp
index 04a06ff732..484fa7b85f 100644
--- a/editor/register_editor_types.cpp
+++ b/editor/register_editor_types.cpp
@@ -282,6 +282,10 @@ void register_editor_types() {
ei_singleton.editor_only = true;
Engine::get_singleton()->add_singleton(ei_singleton);
+ // Required as GDExtensions can register docs at init time way before this
+ // class is actually instantiated.
+ EditorHelp::init_gdext_pointers();
+
OS::get_singleton()->benchmark_end_measure("Editor", "Register Types");
}
diff --git a/editor/window_wrapper.cpp b/editor/window_wrapper.cpp
index a085c2e44f..b2b237269a 100644
--- a/editor/window_wrapper.cpp
+++ b/editor/window_wrapper.cpp
@@ -315,7 +315,7 @@ void WindowWrapper::set_margins_enabled(bool p_enabled) {
}
WindowWrapper::WindowWrapper() {
- if (SceneTree::get_singleton()->get_root()->is_embedding_subwindows() || EDITOR_GET("interface/editor/single_window_mode") || !EDITOR_GET("interface/multi_window/enable")) {
+ if (!EditorNode::get_singleton()->is_multi_window_enabled()) {
return;
}
@@ -375,7 +375,9 @@ void ScreenSelect::_build_advanced_menu() {
}
void ScreenSelect::_emit_screen_signal(int p_screen_idx) {
- emit_signal("request_open_in_screen", p_screen_idx);
+ if (!is_disabled()) {
+ emit_signal("request_open_in_screen", p_screen_idx);
+ }
}
void ScreenSelect::_bind_methods() {
@@ -436,13 +438,19 @@ void ScreenSelect::pressed() {
}
ScreenSelect::ScreenSelect() {
- set_tooltip_text(TTR("Make this panel floating.\nRight click to open the screen selector."));
set_button_mask(MouseButtonMask::RIGHT);
set_flat(true);
set_toggle_mode(true);
set_focus_mode(FOCUS_NONE);
set_action_mode(ACTION_MODE_BUTTON_PRESS);
+ if (!EditorNode::get_singleton()->is_multi_window_enabled()) {
+ set_disabled(true);
+ set_tooltip_text(EditorNode::get_singleton()->get_multiwindow_support_tooltip_text());
+ } else {
+ set_tooltip_text(TTR("Make this panel floating.\nRight-click to open the screen selector."));
+ }
+
// Create the popup.
const Size2 borders = Size2(4, 4) * EDSCALE;
diff --git a/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml b/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml
index 24f6dbd887..e3433e6e29 100644
--- a/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml
+++ b/modules/gltf/doc_classes/EditorSceneFormatImporterBlend.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
Imports Blender scenes in the [code].blend[/code] file format through the glTF 2.0 3D import pipeline. This importer requires Blender to be installed by the user, so that it can be used to export the scene as glTF 2.0.
- The location of the Blender binary is set via the [code]filesystem/import/blender/blender3_path[/code] editor setting.
+ The location of the Blender binary is set via the [code]filesystem/import/blender/blender_path[/code] editor setting.
This importer is only used if [member ProjectSettings.filesystem/import/blender/enabled] is enabled, otherwise [code].blend[/code] files present in the project folder are not imported.
Blend import requires Blender 3.0.
Internally, the EditorSceneFormatImporterBlend uses the Blender glTF "Use Original" mode to reference external textures.
diff --git a/modules/gltf/editor/editor_import_blend_runner.cpp b/modules/gltf/editor/editor_import_blend_runner.cpp
index fc3bb8d11c..330310d92a 100644
--- a/modules/gltf/editor/editor_import_blend_runner.cpp
+++ b/modules/gltf/editor/editor_import_blend_runner.cpp
@@ -153,13 +153,7 @@ String dict_to_xmlrpc(const Dictionary &p_dict) {
}
Error EditorImportBlendRunner::start_blender(const String &p_python_script, bool p_blocking) {
- String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path");
-
-#ifdef WINDOWS_ENABLED
- blender_path = blender_path.path_join("blender.exe");
-#else
- blender_path = blender_path.path_join("blender");
-#endif
+ String blender_path = EDITOR_GET("filesystem/import/blender/blender_path");
List<String> args;
args.push_back("--background");
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 4636782063..a91856c4a1 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -55,20 +55,7 @@
#endif
static bool _get_blender_version(const String &p_path, int &r_major, int &r_minor, String *r_err = nullptr) {
- String path = p_path;
-#ifdef WINDOWS_ENABLED
- path = path.path_join("blender.exe");
-#else
- path = path.path_join("blender");
-#endif
-
-#if defined(MACOS_ENABLED)
- if (!FileAccess::exists(path)) {
- path = p_path.path_join("Blender");
- }
-#endif
-
- if (!FileAccess::exists(path)) {
+ if (!FileAccess::exists(p_path)) {
if (r_err) {
*r_err = TTR("Path does not contain a Blender installation.");
}
@@ -77,7 +64,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
List<String> args;
args.push_back("--version");
String pipe;
- Error err = OS::get_singleton()->execute(path, args, &pipe);
+ Error err = OS::get_singleton()->execute(p_path, args, &pipe);
if (err != OK) {
if (r_err) {
*r_err = TTR("Can't execute Blender binary.");
@@ -87,7 +74,7 @@ static bool _get_blender_version(const String &p_path, int &r_major, int &r_mino
int bl = pipe.find("Blender ");
if (bl == -1) {
if (r_err) {
- *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), path);
+ *r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s."), p_path);
}
return false;
}
@@ -126,7 +113,7 @@ void EditorSceneFormatImporterBlend::get_extensions(List<String> *r_extensions)
Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_t p_flags,
const HashMap<StringName, Variant> &p_options,
List<String> *r_missing_deps, Error *r_err) {
- String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path");
+ String blender_path = EDITOR_GET("filesystem/import/blender/blender_path");
if (blender_major_version == -1 || blender_minor_version == -1) {
_get_blender_version(blender_path, blender_major_version, blender_minor_version, nullptr);
@@ -369,7 +356,7 @@ static bool _test_blender_path(const String &p_path, String *r_err = nullptr) {
bool EditorFileSystemImportFormatSupportQueryBlend::is_active() const {
bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled");
- if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender3_path").operator String())) {
+ if (blend_enabled && !_test_blender_path(EDITOR_GET("filesystem/import/blender/blender_path").operator String())) {
// Intending to import Blender, but blend not configured.
return true;
}
@@ -409,11 +396,59 @@ void EditorFileSystemImportFormatSupportQueryBlend::_validate_path(String p_path
}
}
-bool EditorFileSystemImportFormatSupportQueryBlend::_autodetect_path(String p_path) {
- if (_test_blender_path(p_path)) {
- auto_detected_path = p_path;
- return true;
+bool EditorFileSystemImportFormatSupportQueryBlend::_autodetect_path() {
+ // Autodetect
+ auto_detected_path = "";
+
+#if defined(MACOS_ENABLED)
+ Vector<String> find_paths = {
+ "/opt/homebrew/bin/blender",
+ "/opt/local/bin/blender",
+ "/usr/local/bin/blender",
+ "/usr/local/opt/blender",
+ "/Applications/Blender.app/Contents/MacOS/Blender",
+ };
+ {
+ List<String> mdfind_args;
+ mdfind_args.push_back("kMDItemCFBundleIdentifier=org.blenderfoundation.blender");
+
+ String output;
+ Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output);
+ if (err == OK) {
+ for (const String &find_path : output.split("\n")) {
+ find_paths.push_back(find_path.path_join("Contents/MacOS/Blender"));
+ }
+ }
+ }
+#elif defined(WINDOWS_ENABLED)
+ Vector<String> find_paths = {
+ "C:\\Program Files\\Blender Foundation\\blender.exe",
+ "C:\\Program Files (x86)\\Blender Foundation\\blender.exe",
+ };
+ {
+ char blender_opener_path[MAX_PATH];
+ DWORD path_len = MAX_PATH;
+ HRESULT res = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".blend", "open", blender_opener_path, &path_len);
+ if (res == S_OK) {
+ find_paths.push_back(String(blender_opener_path).get_base_dir().path_join("blender.exe"));
+ }
}
+
+#elif defined(UNIX_ENABLED)
+ Vector<String> find_paths = {
+ "/usr/bin/blender",
+ "/usr/local/bin/blender",
+ "/opt/blender/bin/blender",
+ };
+#endif
+
+ for (const String &find_path : find_paths) {
+ if (_test_blender_path(find_path)) {
+ auto_detected_path = find_path;
+ return true;
+ }
+ }
+
return false;
}
@@ -427,7 +462,7 @@ void EditorFileSystemImportFormatSupportQueryBlend::_select_install(String p_pat
}
void EditorFileSystemImportFormatSupportQueryBlend::_browse_install() {
if (blender_path->get_text() != String()) {
- browse_dialog->set_current_dir(blender_path->get_text());
+ browse_dialog->set_current_file(blender_path->get_text());
}
browse_dialog->popup_centered_ratio();
@@ -479,76 +514,10 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() {
EditorNode::get_singleton()->get_gui_base()->add_child(browse_dialog);
}
- String path = EDITOR_GET("filesystem/import/blender/blender3_path");
+ String path = EDITOR_GET("filesystem/import/blender/blender_path");
- if (path == "") {
- // Autodetect
- auto_detected_path = "";
-
-#if defined(MACOS_ENABLED)
-
- {
- Vector<String> mdfind_paths;
- {
- List<String> mdfind_args;
- mdfind_args.push_back("kMDItemCFBundleIdentifier=org.blenderfoundation.blender");
-
- String output;
- Error err = OS::get_singleton()->execute("mdfind", mdfind_args, &output);
- if (err == OK) {
- mdfind_paths = output.split("\n");
- }
- }
-
- bool found = false;
- for (const String &found_path : mdfind_paths) {
- found = _autodetect_path(found_path.path_join("Contents/MacOS"));
- if (found) {
- break;
- }
- }
- if (!found) {
- found = _autodetect_path("/opt/homebrew/bin");
- }
- if (!found) {
- found = _autodetect_path("/opt/local/bin");
- }
- if (!found) {
- found = _autodetect_path("/usr/local/bin");
- }
- if (!found) {
- found = _autodetect_path("/usr/local/opt");
- }
- if (!found) {
- found = _autodetect_path("/Applications/Blender.app/Contents/MacOS");
- }
- }
-#elif defined(WINDOWS_ENABLED)
- {
- char blender_opener_path[MAX_PATH];
- DWORD path_len = MAX_PATH;
- HRESULT res = AssocQueryString(0, ASSOCSTR_EXECUTABLE, ".blend", "open", blender_opener_path, &path_len);
- if (res == S_OK && _autodetect_path(String(blender_opener_path).get_base_dir())) {
- // Good.
- } else if (_autodetect_path("C:\\Program Files\\Blender Foundation")) {
- // Good.
- } else {
- _autodetect_path("C:\\Program Files (x86)\\Blender Foundation");
- }
- }
-
-#elif defined(UNIX_ENABLED)
- if (_autodetect_path("/usr/bin")) {
- // Good.
- } else if (_autodetect_path("/usr/local/bin")) {
- // Good
- } else {
- _autodetect_path("/opt/blender/bin");
- }
-#endif
- if (auto_detected_path != "") {
- path = auto_detected_path;
- }
+ if (path.is_empty() && _autodetect_path()) {
+ path = auto_detected_path;
}
blender_path->set_text(path);
@@ -569,7 +538,7 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() {
if (confirmed) {
// Can only confirm a valid path.
- EditorSettings::get_singleton()->set("filesystem/import/blender/blender3_path", blender_path->get_text());
+ EditorSettings::get_singleton()->set("filesystem/import/blender/blender_path", blender_path->get_text());
EditorSettings::get_singleton()->save();
} else {
// Disable Blender import
diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h
index c1f4280170..ed1b19eaf3 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.h
+++ b/modules/gltf/editor/editor_scene_importer_blend.h
@@ -95,7 +95,7 @@ class EditorFileSystemImportFormatSupportQueryBlend : public EditorFileSystemImp
String auto_detected_path;
void _validate_path(String p_path);
- bool _autodetect_path(String p_path);
+ bool _autodetect_path();
void _path_confirmed();
diff --git a/modules/gltf/register_types.cpp b/modules/gltf/register_types.cpp
index 94c9d66f78..216309abf1 100644
--- a/modules/gltf/register_types.cpp
+++ b/modules/gltf/register_types.cpp
@@ -55,23 +55,39 @@ static void _editor_init() {
// Blend to glTF importer.
+ String blender_path = EDITOR_GET("filesystem/import/blender/blender_path");
+ if (blender_path.is_empty() && EditorSettings::get_singleton()->has_setting("filesystem/import/blender/blender3_path")) {
+ blender_path = EditorSettings::get_singleton()->get("filesystem/import/blender/blender3_path");
+
+ if (!blender_path.is_empty()) {
+#if defined(MACOS_ENABLED)
+ if (blender_path.contains(".app")) {
+ blender_path += "/Contents/MacOS/Blender";
+ } else {
+ blender_path += "/blender";
+ }
+#elif defined(WINDOWS_ENABLED)
+ blender_path += "\\blender.exe";
+#elif defined(UNIX_ENABLED)
+ blender_path += "/blender";
+#endif
+
+ EditorSettings::get_singleton()->set("filesystem/import/blender/blender_path", blender_path);
+ }
+
+ EditorSettings::get_singleton()->erase("filesystem/import/blender/blender3_path");
+ EditorSettings::get_singleton()->save();
+ }
+
bool blend_enabled = GLOBAL_GET("filesystem/import/blender/enabled");
- String blender3_path = EDITOR_GET("filesystem/import/blender/blender3_path");
if (blend_enabled) {
- Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- if (blender3_path.is_empty()) {
- WARN_PRINT(TTR("Blend file import is enabled in the project settings, but no Blender path is configured in the editor settings. Blend files will not be imported."));
- } else if (!da->dir_exists(blender3_path)) {
- WARN_PRINT(TTR("Blend file import is enabled, but the Blender path doesn't point to an accessible directory. Blend files will not be imported."));
- } else {
- Ref<EditorSceneFormatImporterBlend> importer;
- importer.instantiate();
- ResourceImporterScene::add_scene_importer(importer);
-
- Ref<EditorFileSystemImportFormatSupportQueryBlend> blend_import_query;
- blend_import_query.instantiate();
- EditorFileSystem::get_singleton()->add_import_format_support_query(blend_import_query);
- }
+ Ref<EditorSceneFormatImporterBlend> importer;
+ importer.instantiate();
+ ResourceImporterScene::add_scene_importer(importer);
+
+ Ref<EditorFileSystemImportFormatSupportQueryBlend> blend_import_query;
+ blend_import_query.instantiate();
+ EditorFileSystem::get_singleton()->add_import_format_support_query(blend_import_query);
}
memnew(EditorImportBlendRunner);
EditorNode::get_singleton()->add_child(EditorImportBlendRunner::get_singleton());
diff --git a/platform/android/android_input_handler.cpp b/platform/android/android_input_handler.cpp
index bd194478d9..8e7f355114 100644
--- a/platform/android/android_input_handler.cpp
+++ b/platform/android/android_input_handler.cpp
@@ -124,6 +124,7 @@ void AndroidInputHandler::process_key_event(int p_physical_keycode, int p_unicod
ev->set_physical_keycode(physical_keycode);
ev->set_key_label(fix_key_label(p_key_label, keycode));
ev->set_unicode(fix_unicode(unicode));
+ ev->set_location(godot_location_from_android_code(p_physical_keycode));
ev->set_pressed(p_pressed);
ev->set_echo(p_echo);
diff --git a/platform/android/android_keys_utils.cpp b/platform/android/android_keys_utils.cpp
index f50437e82a..83ee98e8bc 100644
--- a/platform/android/android_keys_utils.cpp
+++ b/platform/android/android_keys_utils.cpp
@@ -38,3 +38,12 @@ Key godot_code_from_android_code(unsigned int p_code) {
}
return Key::UNKNOWN;
}
+
+KeyLocation godot_location_from_android_code(unsigned int p_code) {
+ for (int i = 0; android_godot_location_pairs[i].android_code != AKEYCODE_MAX; i++) {
+ if (android_godot_location_pairs[i].android_code == p_code) {
+ return android_godot_location_pairs[i].godot_code;
+ }
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/android/android_keys_utils.h b/platform/android/android_keys_utils.h
index 5cf5628a8b..77c0911f2b 100644
--- a/platform/android/android_keys_utils.h
+++ b/platform/android/android_keys_utils.h
@@ -177,4 +177,24 @@ static AndroidGodotCodePair android_godot_code_pairs[] = {
Key godot_code_from_android_code(unsigned int p_code);
+// Key location determination.
+struct AndroidGodotLocationPair {
+ unsigned int android_code = 0;
+ KeyLocation godot_code = KeyLocation::UNSPECIFIED;
+};
+
+static AndroidGodotLocationPair android_godot_location_pairs[] = {
+ { AKEYCODE_ALT_LEFT, KeyLocation::LEFT },
+ { AKEYCODE_ALT_RIGHT, KeyLocation::RIGHT },
+ { AKEYCODE_SHIFT_LEFT, KeyLocation::LEFT },
+ { AKEYCODE_SHIFT_RIGHT, KeyLocation::RIGHT },
+ { AKEYCODE_CTRL_LEFT, KeyLocation::LEFT },
+ { AKEYCODE_CTRL_RIGHT, KeyLocation::RIGHT },
+ { AKEYCODE_META_LEFT, KeyLocation::LEFT },
+ { AKEYCODE_META_RIGHT, KeyLocation::RIGHT },
+ { AKEYCODE_MAX, KeyLocation::UNSPECIFIED }
+};
+
+KeyLocation godot_location_from_android_code(unsigned int p_code);
+
#endif // ANDROID_KEYS_UTILS_H
diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h
index 3d19222fa8..3fdcc07f0b 100644
--- a/platform/ios/display_server_ios.h
+++ b/platform/ios/display_server_ios.h
@@ -119,7 +119,7 @@ public:
// MARK: Keyboard
- void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed);
+ void key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location);
bool is_keyboard_active() const;
// MARK: Motion
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index c31f503605..c660dc5697 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -247,7 +247,7 @@ void DisplayServerIOS::touches_canceled(int p_idx) {
// MARK: Keyboard
-void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed) {
+void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_physical, NSInteger p_modifier, bool p_pressed, KeyLocation p_location) {
Ref<InputEventKey> ev;
ev.instantiate();
ev->set_echo(false);
@@ -270,6 +270,7 @@ void DisplayServerIOS::key(Key p_key, char32_t p_char, Key p_unshifted, Key p_ph
ev->set_key_label(p_unshifted);
ev->set_physical_keycode(p_physical);
ev->set_unicode(fix_unicode(p_char));
+ ev->set_location(p_location);
perform_event(ev);
}
diff --git a/platform/ios/key_mapping_ios.h b/platform/ios/key_mapping_ios.h
index 6cc61175bb..8874da3024 100644
--- a/platform/ios/key_mapping_ios.h
+++ b/platform/ios/key_mapping_ios.h
@@ -41,6 +41,7 @@ class KeyMappingIOS {
public:
static void initialize();
static Key remap_key(CFIndex p_keycode);
+ static KeyLocation key_location(CFIndex p_keycode);
};
#endif // KEY_MAPPING_IOS_H
diff --git a/platform/ios/key_mapping_ios.mm b/platform/ios/key_mapping_ios.mm
index d2c84884d1..61f28aa84b 100644
--- a/platform/ios/key_mapping_ios.mm
+++ b/platform/ios/key_mapping_ios.mm
@@ -38,6 +38,7 @@ struct HashMapHasherKeys {
};
HashMap<CFIndex, Key, HashMapHasherKeys> keyusage_map;
+HashMap<CFIndex, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingIOS::initialize() {
if (@available(iOS 13.4, *)) {
@@ -172,6 +173,15 @@ void KeyMappingIOS::initialize() {
keyusage_map[0x029D] = Key::GLOBE; // "Globe" key on smart connector / Mac keyboard.
keyusage_map[UIKeyboardHIDUsageKeyboardLANG1] = Key::JIS_EISU;
keyusage_map[UIKeyboardHIDUsageKeyboardLANG2] = Key::JIS_KANA;
+
+ location_map[UIKeyboardHIDUsageKeyboardLeftAlt] = KeyLocation::LEFT;
+ location_map[UIKeyboardHIDUsageKeyboardRightAlt] = KeyLocation::RIGHT;
+ location_map[UIKeyboardHIDUsageKeyboardLeftControl] = KeyLocation::LEFT;
+ location_map[UIKeyboardHIDUsageKeyboardRightControl] = KeyLocation::RIGHT;
+ location_map[UIKeyboardHIDUsageKeyboardLeftShift] = KeyLocation::LEFT;
+ location_map[UIKeyboardHIDUsageKeyboardRightShift] = KeyLocation::RIGHT;
+ location_map[UIKeyboardHIDUsageKeyboardLeftGUI] = KeyLocation::LEFT;
+ location_map[UIKeyboardHIDUsageKeyboardRightGUI] = KeyLocation::RIGHT;
}
}
@@ -184,3 +194,13 @@ Key KeyMappingIOS::remap_key(CFIndex p_keycode) {
}
return Key::NONE;
}
+
+KeyLocation KeyMappingIOS::key_location(CFIndex p_keycode) {
+ if (@available(iOS 13.4, *)) {
+ const KeyLocation *location = location_map.getptr(p_keycode);
+ if (location) {
+ return *location;
+ }
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/ios/keyboard_input_view.mm b/platform/ios/keyboard_input_view.mm
index bc6eb63ed5..8b614662b7 100644
--- a/platform/ios/keyboard_input_view.mm
+++ b/platform/ios/keyboard_input_view.mm
@@ -116,8 +116,8 @@
- (void)deleteText:(NSInteger)charactersToDelete {
for (int i = 0; i < charactersToDelete; i++) {
- DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true);
- DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false);
+ DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
+ DisplayServerIOS::get_singleton()->key(Key::BACKSPACE, 0, Key::BACKSPACE, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
}
}
@@ -137,8 +137,8 @@
key = Key::SPACE;
}
- DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true);
- DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false);
+ DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, true, KeyLocation::UNSPECIFIED);
+ DisplayServerIOS::get_singleton()->key(key, character, key, Key::NONE, 0, false, KeyLocation::UNSPECIFIED);
}
}
diff --git a/platform/ios/view_controller.mm b/platform/ios/view_controller.mm
index 1f55670b68..6f6c04c2c8 100644
--- a/platform/ios/view_controller.mm
+++ b/platform/ios/view_controller.mm
@@ -78,13 +78,15 @@
us = u32lbl[0];
}
+ KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode);
+
if (!u32text.is_empty() && !u32text.begins_with("UIKey")) {
for (int i = 0; i < u32text.length(); i++) {
const char32_t c = u32text[i];
- DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true);
+ DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), c, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
}
} else {
- DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true);
+ DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, true, location);
}
}
}
@@ -110,7 +112,9 @@
us = u32lbl[0];
}
- DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false);
+ KeyLocation location = KeyMappingIOS::key_location(press.key.keyCode);
+
+ DisplayServerIOS::get_singleton()->key(fix_keycode(us, key), 0, fix_key_label(us, key), key, press.key.modifierFlags, false, location);
}
}
}
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index f49f00e60f..20e2e897f2 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -3512,6 +3512,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
bool keypress = xkeyevent->type == KeyPress;
Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
+ KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);
if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
keycode -= 'a' - 'A';
@@ -3549,6 +3550,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
k->set_unicode(fix_unicode(tmp[i]));
}
+ k->set_location(key_location);
+
k->set_echo(false);
if (k->get_keycode() == Key::BACKTAB) {
@@ -3574,6 +3577,8 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
+ KeyLocation key_location = KeyMappingX11::get_location(xkeyevent->keycode);
+
/* Phase 3, obtain a unicode character from the keysym */
// KeyMappingX11 also translates keysym to unicode.
@@ -3673,6 +3678,9 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
if (keypress) {
k->set_unicode(fix_unicode(unicode));
}
+
+ k->set_location(key_location);
+
k->set_echo(p_echo);
if (k->get_keycode() == Key::BACKTAB) {
diff --git a/platform/linuxbsd/x11/key_mapping_x11.cpp b/platform/linuxbsd/x11/key_mapping_x11.cpp
index c0e6b91d57..b589a2a573 100644
--- a/platform/linuxbsd/x11/key_mapping_x11.cpp
+++ b/platform/linuxbsd/x11/key_mapping_x11.cpp
@@ -1113,6 +1113,20 @@ void KeyMappingX11::initialize() {
xkeysym_unicode_map[0x13BD] = 0x0153;
xkeysym_unicode_map[0x13BE] = 0x0178;
xkeysym_unicode_map[0x20AC] = 0x20AC;
+
+ // Scancode to physical location map.
+ // Ctrl.
+ location_map[0x25] = KeyLocation::LEFT;
+ location_map[0x69] = KeyLocation::RIGHT;
+ // Shift.
+ location_map[0x32] = KeyLocation::LEFT;
+ location_map[0x3E] = KeyLocation::RIGHT;
+ // Alt.
+ location_map[0x40] = KeyLocation::LEFT;
+ location_map[0x6C] = KeyLocation::RIGHT;
+ // Meta.
+ location_map[0x85] = KeyLocation::LEFT;
+ location_map[0x86] = KeyLocation::RIGHT;
}
Key KeyMappingX11::get_keycode(KeySym p_keysym) {
@@ -1173,3 +1187,11 @@ char32_t KeyMappingX11::get_unicode_from_keysym(KeySym p_keysym) {
}
return 0;
}
+
+KeyLocation KeyMappingX11::get_location(unsigned int p_code) {
+ const KeyLocation *location = location_map.getptr(p_code);
+ if (location) {
+ return *location;
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/linuxbsd/x11/key_mapping_x11.h b/platform/linuxbsd/x11/key_mapping_x11.h
index ae8fd67f27..a51ee5f48e 100644
--- a/platform/linuxbsd/x11/key_mapping_x11.h
+++ b/platform/linuxbsd/x11/key_mapping_x11.h
@@ -54,6 +54,7 @@ class KeyMappingX11 {
static inline HashMap<unsigned int, Key, HashMapHasherKeys> scancode_map;
static inline HashMap<Key, unsigned int, HashMapHasherKeys> scancode_map_inv;
static inline HashMap<KeySym, char32_t, HashMapHasherKeys> xkeysym_unicode_map;
+ static inline HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
KeyMappingX11() {}
@@ -64,6 +65,7 @@ public:
static unsigned int get_xlibcode(Key p_keysym);
static Key get_scancode(unsigned int p_code);
static char32_t get_unicode_from_keysym(KeySym p_keysym);
+ static KeyLocation get_location(unsigned int p_code);
};
#endif // KEY_MAPPING_X11_H
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index cdd17a7e76..24d4a349e2 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -75,6 +75,7 @@ public:
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
uint32_t unicode = 0;
+ KeyLocation location = KeyLocation::UNSPECIFIED;
};
struct WindowData {
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 2bfe16828a..396b943251 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -478,6 +478,7 @@ void DisplayServerMacOS::_process_key_events() {
k->set_physical_keycode(ke.physical_keycode);
k->set_key_label(ke.key_label);
k->set_unicode(ke.unicode);
+ k->set_location(ke.location);
_push_input(k);
} else {
@@ -506,6 +507,7 @@ void DisplayServerMacOS::_process_key_events() {
k->set_keycode(ke.keycode);
k->set_physical_keycode(ke.physical_keycode);
k->set_key_label(ke.key_label);
+ k->set_location(ke.location);
if (i + 1 < key_event_pos && key_event_buffer[i + 1].keycode == Key::NONE) {
k->set_unicode(key_event_buffer[i + 1].unicode);
diff --git a/platform/macos/godot_content_view.mm b/platform/macos/godot_content_view.mm
index 61065291d7..4505becbc2 100644
--- a/platform/macos/godot_content_view.mm
+++ b/platform/macos/godot_content_view.mm
@@ -606,6 +606,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
ke.unicode = 0;
+ ke.location = KeyMappingMacOS::translate_location([event keyCode]);
ke.raw = false;
ds->push_to_key_event_buffer(ke);
@@ -671,6 +672,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key(key);
ke.key_label = KeyMappingMacOS::remap_key(key, mod, true);
ke.unicode = 0;
+ ke.location = KeyMappingMacOS::translate_location(key);
ds->push_to_key_event_buffer(ke);
}
@@ -698,6 +700,7 @@
ke.physical_keycode = KeyMappingMacOS::translate_key([event keyCode]);
ke.key_label = KeyMappingMacOS::remap_key([event keyCode], [event modifierFlags], true);
ke.unicode = 0;
+ ke.location = KeyMappingMacOS::translate_location([event keyCode]);
ke.raw = true;
ds->push_to_key_event_buffer(ke);
diff --git a/platform/macos/key_mapping_macos.h b/platform/macos/key_mapping_macos.h
index 1bda4eb406..f5b0ff8d02 100644
--- a/platform/macos/key_mapping_macos.h
+++ b/platform/macos/key_mapping_macos.h
@@ -45,6 +45,7 @@ public:
static Key translate_key(unsigned int p_key);
static unsigned int unmap_key(Key p_key);
static Key remap_key(unsigned int p_key, unsigned int p_state, bool p_unicode);
+ static KeyLocation translate_location(unsigned int p_key);
// Mapping for menu shortcuts.
static String keycode_get_native_string(Key p_keycode);
diff --git a/platform/macos/key_mapping_macos.mm b/platform/macos/key_mapping_macos.mm
index db3fa4e02d..b5e72048e7 100644
--- a/platform/macos/key_mapping_macos.mm
+++ b/platform/macos/key_mapping_macos.mm
@@ -46,6 +46,7 @@ HashSet<unsigned int> numpad_keys;
HashMap<unsigned int, Key, HashMapHasherKeys> keysym_map;
HashMap<Key, unsigned int, HashMapHasherKeys> keysym_map_inv;
HashMap<Key, char32_t, HashMapHasherKeys> keycode_map;
+HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingMacOS::initialize() {
numpad_keys.insert(0x41); //kVK_ANSI_KeypadDecimal
@@ -321,6 +322,20 @@ void KeyMappingMacOS::initialize() {
keycode_map[Key::BAR] = '|';
keycode_map[Key::BRACERIGHT] = '}';
keycode_map[Key::ASCIITILDE] = '~';
+
+ // Keysym -> physical location.
+ // Ctrl.
+ location_map[0x3b] = KeyLocation::LEFT;
+ location_map[0x3e] = KeyLocation::RIGHT;
+ // Shift.
+ location_map[0x38] = KeyLocation::LEFT;
+ location_map[0x3c] = KeyLocation::RIGHT;
+ // Alt/Option.
+ location_map[0x3a] = KeyLocation::LEFT;
+ location_map[0x3d] = KeyLocation::RIGHT;
+ // Meta/Command (yes, right < left).
+ location_map[0x36] = KeyLocation::RIGHT;
+ location_map[0x37] = KeyLocation::LEFT;
}
bool KeyMappingMacOS::is_numpad_key(unsigned int p_key) {
@@ -396,6 +411,15 @@ Key KeyMappingMacOS::remap_key(unsigned int p_key, unsigned int p_state, bool p_
}
}
+// Translates a macOS keycode to a Godot key location.
+KeyLocation KeyMappingMacOS::translate_location(unsigned int p_key) {
+ const KeyLocation *location = location_map.getptr(p_key);
+ if (location) {
+ return *location;
+ }
+ return KeyLocation::UNSPECIFIED;
+}
+
String KeyMappingMacOS::keycode_get_native_string(Key p_keycode) {
const char32_t *key = keycode_map.getptr(p_keycode);
if (key) {
diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp
index b4a190d47e..aacbe4879f 100644
--- a/platform/web/display_server_web.cpp
+++ b/platform/web/display_server_web.cpp
@@ -187,6 +187,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin
Key keycode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), false);
Key scancode = dom_code2godot_scancode(p_key_event_code.utf8().get_data(), p_key_event_key.utf8().get_data(), true);
+ KeyLocation location = dom_code2godot_key_location(p_key_event_code.utf8().get_data());
DisplayServerWeb::KeyEvent ke;
@@ -197,6 +198,7 @@ void DisplayServerWeb::_key_callback(const String &p_key_event_code, const Strin
ke.physical_keycode = scancode;
ke.key_label = fix_key_label(c, keycode);
ke.unicode = fix_unicode(c);
+ ke.location = location;
ke.mod = p_modifiers;
if (ds->key_event_pos >= ds->key_event_buffer.size()) {
@@ -1383,6 +1385,7 @@ void DisplayServerWeb::process_events() {
ev->set_physical_keycode(ke.physical_keycode);
ev->set_key_label(ke.key_label);
ev->set_unicode(ke.unicode);
+ ev->set_location(ke.location);
if (ke.raw) {
dom2godot_mod(ev, ke.mod, ke.keycode);
}
diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h
index 140aef952b..682d10704f 100644
--- a/platform/web/display_server_web.h
+++ b/platform/web/display_server_web.h
@@ -95,6 +95,7 @@ private:
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
uint32_t unicode = 0;
+ KeyLocation location = KeyLocation::UNSPECIFIED;
int mod = 0;
};
diff --git a/platform/web/dom_keys.inc b/platform/web/dom_keys.inc
index cd94b779c0..b20a3a46b9 100644
--- a/platform/web/dom_keys.inc
+++ b/platform/web/dom_keys.inc
@@ -223,3 +223,24 @@ Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b
return Key::NONE;
#undef DOM2GODOT
}
+
+KeyLocation dom_code2godot_key_location(EM_UTF8 const p_code[32]) {
+#define DOM2GODOT(m_str, m_godot_code) \
+ if (memcmp((const void *)m_str, (void *)p_code, strlen(m_str) + 1) == 0) { \
+ return KeyLocation::m_godot_code; \
+ }
+
+ DOM2GODOT("AltLeft", LEFT);
+ DOM2GODOT("AltRight", RIGHT);
+ DOM2GODOT("ControlLeft", LEFT);
+ DOM2GODOT("ControlRight", RIGHT);
+ DOM2GODOT("MetaLeft", LEFT);
+ DOM2GODOT("MetaRight", RIGHT);
+ DOM2GODOT("OSLeft", LEFT);
+ DOM2GODOT("OSRight", RIGHT);
+ DOM2GODOT("ShiftLeft", LEFT);
+ DOM2GODOT("ShiftRight", RIGHT);
+
+ return KeyLocation::UNSPECIFIED;
+#undef DOM2GODOT
+}
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 1c9b86d35d..657caa7939 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -169,20 +169,26 @@ DisplayServer::WindowID DisplayServerWindows::_get_focused_window_or_popup() con
void DisplayServerWindows::_register_raw_input_devices(WindowID p_target_window) {
use_raw_input = true;
- RAWINPUTDEVICE rid[1] = {};
- rid[0].usUsagePage = 0x01;
- rid[0].usUsage = 0x02;
+ RAWINPUTDEVICE rid[2] = {};
+ rid[0].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
+ rid[0].usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE
rid[0].dwFlags = 0;
+ rid[1].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC
+ rid[1].usUsage = 0x06; // HID_USAGE_GENERIC_KEYBOARD
+ rid[1].dwFlags = 0;
+
if (p_target_window != INVALID_WINDOW_ID && windows.has(p_target_window)) {
// Follow the defined window
rid[0].hwndTarget = windows[p_target_window].hWnd;
+ rid[1].hwndTarget = windows[p_target_window].hWnd;
} else {
// Follow the keyboard focus
rid[0].hwndTarget = 0;
+ rid[1].hwndTarget = 0;
}
- if (RegisterRawInputDevices(rid, 1, sizeof(rid[0])) == FALSE) {
+ if (RegisterRawInputDevices(rid, 2, sizeof(rid[0])) == FALSE) {
// Registration failed.
use_raw_input = false;
}
@@ -3348,7 +3354,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
} break;
case WM_INPUT: {
- if (mouse_mode != MOUSE_MODE_CAPTURED || !use_raw_input) {
+ if (!use_raw_input) {
break;
}
@@ -3366,7 +3372,32 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
RAWINPUT *raw = (RAWINPUT *)lpb;
- if (raw->header.dwType == RIM_TYPEMOUSE) {
+ if (raw->header.dwType == RIM_TYPEKEYBOARD) {
+ if (raw->data.keyboard.VKey == VK_SHIFT) {
+ // If multiple Shifts are held down at the same time,
+ // Windows natively only sends a KEYUP for the last one to be released.
+ if (raw->data.keyboard.Flags & RI_KEY_BREAK) {
+ if (GetAsyncKeyState(VK_SHIFT) < 0) {
+ // A Shift is released, but another Shift is still held
+ ERR_BREAK(key_event_pos >= KEY_EVENT_BUFFER_SIZE);
+
+ KeyEvent ke;
+ ke.shift = false;
+ ke.alt = alt_mem;
+ ke.control = control_mem;
+ ke.meta = meta_mem;
+ ke.uMsg = WM_KEYUP;
+ ke.window_id = window_id;
+
+ ke.wParam = VK_SHIFT;
+ // data.keyboard.MakeCode -> 0x2A - left shift, 0x36 - right shift.
+ // Bit 30 -> key was previously down, bit 31 -> key is being released.
+ ke.lParam = raw->data.keyboard.MakeCode << 16 | 1 << 30 | 1 << 31;
+ key_event_buffer[key_event_pos++] = ke;
+ }
+ }
+ }
+ } else if (mouse_mode == MOUSE_MODE_CAPTURED && raw->header.dwType == RIM_TYPEMOUSE) {
Ref<InputEventMouseMotion> mm;
mm.instantiate();
@@ -4371,6 +4402,7 @@ void DisplayServerWindows::_process_key_events() {
}
Key key_label = keycode;
Key physical_keycode = KeyMappingWindows::get_scansym((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24));
+ KeyLocation location = KeyMappingWindows::get_location((ke.lParam >> 16) & 0xFF, ke.lParam & (1 << 24));
static BYTE keyboard_state[256];
memset(keyboard_state, 0, 256);
@@ -4397,6 +4429,7 @@ void DisplayServerWindows::_process_key_events() {
}
k->set_keycode(keycode);
k->set_physical_keycode(physical_keycode);
+ k->set_location(location);
k->set_key_label(key_label);
if (i + 1 < key_event_pos && key_event_buffer[i + 1].uMsg == WM_CHAR) {
diff --git a/platform/windows/key_mapping_windows.cpp b/platform/windows/key_mapping_windows.cpp
index b376854c0c..20905d0fe9 100644
--- a/platform/windows/key_mapping_windows.cpp
+++ b/platform/windows/key_mapping_windows.cpp
@@ -46,6 +46,7 @@ HashMap<unsigned int, Key, HashMapHasherKeys> vk_map;
HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map;
HashMap<Key, unsigned int, HashMapHasherKeys> scansym_map_inv;
HashMap<unsigned int, Key, HashMapHasherKeys> scansym_map_ext;
+HashMap<unsigned int, KeyLocation, HashMapHasherKeys> location_map;
void KeyMappingWindows::initialize() {
// VK_LBUTTON (0x01)
@@ -380,6 +381,15 @@ void KeyMappingWindows::initialize() {
scansym_map_ext[0x6C] = Key::LAUNCHMAIL;
scansym_map_ext[0x6D] = Key::LAUNCHMEDIA;
scansym_map_ext[0x78] = Key::MEDIARECORD;
+
+ // Scancode to physical location map.
+ // Shift.
+ location_map[0x2A] = KeyLocation::LEFT;
+ location_map[0x36] = KeyLocation::RIGHT;
+ // Meta.
+ location_map[0x5B] = KeyLocation::LEFT;
+ location_map[0x5C] = KeyLocation::RIGHT;
+ // Ctrl and Alt must be handled differently.
}
Key KeyMappingWindows::get_keysym(unsigned int p_code) {
@@ -424,3 +434,16 @@ bool KeyMappingWindows::is_extended_key(unsigned int p_code) {
p_code == VK_RIGHT ||
p_code == VK_DOWN;
}
+
+KeyLocation KeyMappingWindows::get_location(unsigned int p_code, bool p_extended) {
+ // Right- ctrl and alt have the same scancode as left, but are in the extended keys.
+ const Key *key = scansym_map.getptr(p_code);
+ if (key && (*key == Key::CTRL || *key == Key::ALT)) {
+ return p_extended ? KeyLocation::RIGHT : KeyLocation::LEFT;
+ }
+ const KeyLocation *location = location_map.getptr(p_code);
+ if (location) {
+ return *location;
+ }
+ return KeyLocation::UNSPECIFIED;
+}
diff --git a/platform/windows/key_mapping_windows.h b/platform/windows/key_mapping_windows.h
index a98aa7ed68..e6f184a2cc 100644
--- a/platform/windows/key_mapping_windows.h
+++ b/platform/windows/key_mapping_windows.h
@@ -47,6 +47,7 @@ public:
static unsigned int get_scancode(Key p_keycode);
static Key get_scansym(unsigned int p_code, bool p_extended);
static bool is_extended_key(unsigned int p_code);
+ static KeyLocation get_location(unsigned int p_code, bool p_extended);
};
#endif // KEY_MAPPING_WINDOWS_H
diff --git a/tests/core/input/test_input_event_key.h b/tests/core/input/test_input_event_key.h
index 3317941fad..80918542ce 100644
--- a/tests/core/input/test_input_event_key.h
+++ b/tests/core/input/test_input_event_key.h
@@ -85,6 +85,16 @@ TEST_CASE("[InputEventKey] Key correctly stores and retrieves unicode") {
CHECK(key.get_unicode() != 'y');
}
+TEST_CASE("[InputEventKey] Key correctly stores and retrieves location") {
+ InputEventKey key;
+
+ CHECK(key.get_location() == KeyLocation::UNSPECIFIED);
+
+ key.set_location(KeyLocation::LEFT);
+ CHECK(key.get_location() == KeyLocation::LEFT);
+ CHECK(key.get_location() != KeyLocation::RIGHT);
+}
+
TEST_CASE("[InputEventKey] Key correctly stores and checks echo") {
InputEventKey key;
@@ -144,32 +154,36 @@ TEST_CASE("[InputEventKey] Key correctly converts itself to text") {
TEST_CASE("[InputEventKey] Key correctly converts its state to a string representation") {
InputEventKey none_key;
- CHECK(none_key.to_string() == "InputEventKey: keycode=(Unset), mods=none, physical=false, pressed=false, echo=false");
+ CHECK(none_key.to_string() == "InputEventKey: keycode=(Unset), mods=none, physical=false, location=unspecified, pressed=false, echo=false");
// Set physical key to Escape.
none_key.set_physical_keycode(Key::ESCAPE);
- CHECK(none_key.to_string() == "InputEventKey: keycode=4194305 (Escape), mods=none, physical=true, pressed=false, echo=false");
+ CHECK(none_key.to_string() == "InputEventKey: keycode=4194305 (Escape), mods=none, physical=true, location=unspecified, pressed=false, echo=false");
InputEventKey key;
// Set physical to None, set keycode to Space.
key.set_keycode(Key::SPACE);
- CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=false, echo=false");
+ CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=unspecified, pressed=false, echo=false");
+
+ // Set location
+ key.set_location(KeyLocation::RIGHT);
+ CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=false, echo=false");
// Set pressed to true.
key.set_pressed(true);
- CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=true, echo=false");
+ CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=true, echo=false");
// set echo to true.
key.set_echo(true);
- CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, pressed=true, echo=true");
+ CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=none, physical=false, location=right, pressed=true, echo=true");
// Press Ctrl and Alt.
key.set_ctrl_pressed(true);
key.set_alt_pressed(true);
#ifdef MACOS_ENABLED
- CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Option, physical=false, pressed=true, echo=true");
+ CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Option, physical=false, location=right, pressed=true, echo=true");
#else
- CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Alt, physical=false, pressed=true, echo=true");
+ CHECK(key.to_string() == "InputEventKey: keycode=32 (Space), mods=Ctrl+Alt, physical=false, location=right, pressed=true, echo=true");
#endif
}
@@ -291,6 +305,34 @@ TEST_CASE("[IsMatch] Keys are correctly matched") {
CHECK(key2.is_match(match, true) == true);
CHECK(key2.is_match(no_match, true) == false);
+
+ // Physical key with location.
+ InputEventKey key3;
+ key3.set_keycode(Key::NONE);
+ key3.set_physical_keycode(Key::SHIFT);
+
+ Ref<InputEventKey> loc_ref = key.create_reference(Key::NONE);
+
+ loc_ref->set_keycode(Key::SHIFT);
+ loc_ref->set_physical_keycode(Key::SHIFT);
+
+ CHECK(key3.is_match(loc_ref, false) == true);
+ key3.set_location(KeyLocation::UNSPECIFIED);
+ CHECK(key3.is_match(loc_ref, false) == true);
+
+ loc_ref->set_location(KeyLocation::LEFT);
+ CHECK(key3.is_match(loc_ref, false) == true);
+
+ key3.set_location(KeyLocation::LEFT);
+ CHECK(key3.is_match(loc_ref, false) == true);
+
+ key3.set_location(KeyLocation::RIGHT);
+ CHECK(key3.is_match(loc_ref, false) == false);
+
+ // Keycode key with location.
+ key3.set_physical_keycode(Key::NONE);
+ key3.set_keycode(Key::SHIFT);
+ CHECK(key3.is_match(loc_ref, false) == true);
}
} // namespace TestInputEventKey