summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/class_db.cpp18
-rw-r--r--core/object.h1
-rw-r--r--doc/classes/Array.xml10
-rw-r--r--doc/classes/Dictionary.xml7
-rw-r--r--doc/classes/DisplayServer.xml66
-rw-r--r--doc/classes/Image.xml2
-rw-r--r--doc/classes/Label.xml2
-rw-r--r--doc/classes/Material.xml2
-rw-r--r--doc/classes/Node.xml5
-rw-r--r--doc/classes/Object.xml1
-rw-r--r--doc/classes/String.xml14
-rw-r--r--doc/classes/SubViewportContainer.xml1
-rw-r--r--doc/classes/TileSet.xml2
-rw-r--r--editor/code_editor.cpp9
-rw-r--r--editor/create_dialog.cpp57
-rw-r--r--editor/editor_audio_buses.cpp13
-rw-r--r--editor/editor_audio_buses.h1
-rw-r--r--editor/import/editor_scene_importer_gltf.cpp131
-rw-r--r--editor/import/editor_scene_importer_gltf.h19
-rw-r--r--editor/import/resource_importer_texture.cpp7
-rw-r--r--editor/import/resource_importer_texture.h1
-rw-r--r--editor/plugins/skeleton_3d_editor_plugin.cpp1
-rw-r--r--editor/project_manager.cpp4
-rw-r--r--modules/regex/doc_classes/RegEx.xml3
-rw-r--r--modules/regex/doc_classes/RegExMatch.xml2
-rw-r--r--platform/javascript/javascript_main.cpp12
-rw-r--r--platform/linuxbsd/display_server_x11.cpp119
-rw-r--r--platform/linuxbsd/display_server_x11.h6
-rw-r--r--platform/osx/display_server_osx.h6
-rw-r--r--platform/osx/display_server_osx.mm152
-rw-r--r--platform/osx/export/export.cpp119
-rw-r--r--platform/uwp/export/export.cpp18
-rw-r--r--platform/windows/display_server_windows.cpp137
-rw-r--r--platform/windows/display_server_windows.h6
-rw-r--r--scene/2d/path_2d.cpp6
-rw-r--r--scene/2d/path_2d.h2
-rw-r--r--scene/3d/path_3d.cpp6
-rw-r--r--scene/3d/path_3d.h2
-rw-r--r--scene/gui/rich_text_label.cpp14
-rw-r--r--scene/gui/tab_container.cpp45
-rw-r--r--scene/gui/tab_container.h1
-rw-r--r--servers/display_server.cpp33
-rw-r--r--servers/display_server.h17
-rw-r--r--servers/physics_2d/broad_phase_2d_hash_grid.cpp5
44 files changed, 795 insertions, 290 deletions
diff --git a/core/class_db.cpp b/core/class_db.cpp
index eed9ec17cb..05c9850c39 100644
--- a/core/class_db.cpp
+++ b/core/class_db.cpp
@@ -1386,7 +1386,23 @@ Variant ClassDB::class_get_default_property_value(const StringName &p_class, con
if (r_valid != nullptr) {
*r_valid = true;
}
- return default_values[p_class][p_property];
+
+ Variant var = default_values[p_class][p_property];
+
+#ifdef DEBUG_ENABLED
+ // Some properties may have an instantiated Object as default value,
+ // (like Path2D's `curve` used to have), but that's not a good practice.
+ // Instead, those properties should use PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT
+ // to be auto-instantiated when created in the editor.
+ if (var.get_type() == Variant::OBJECT) {
+ Object *obj = var.get_validated_object();
+ if (obj) {
+ WARN_PRINT(vformat("Instantiated %s used as default value for %s's \"%s\" property.", obj->get_class(), p_class, p_property));
+ }
+ }
+#endif
+
+ return var;
}
RWLock *ClassDB::lock = nullptr;
diff --git a/core/object.h b/core/object.h
index 95662f6208..5b46a0f93a 100644
--- a/core/object.h
+++ b/core/object.h
@@ -124,6 +124,7 @@ enum PropertyUsageFlags {
PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT = 1 << 24,
PROPERTY_USAGE_KEYING_INCREMENTS = 1 << 25, // Used in inspector to increment property when keyed in animation player
PROPERTY_USAGE_DEFERRED_SET_RESOURCE = 1 << 26, // when loading, the resource for this property can be set at the end of loading
+ PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT = 1 << 27, // For Object properties, instantiate them when creating in editor.
PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_NETWORK,
PROPERTY_USAGE_DEFAULT_INTL = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_NETWORK | PROPERTY_USAGE_INTERNATIONALIZED,
diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml
index 7593f7dff4..9a3eccd8dc 100644
--- a/doc/classes/Array.xml
+++ b/doc/classes/Array.xml
@@ -200,7 +200,7 @@
<argument index="1" name="from" type="int" default="0">
</argument>
<description>
- Searches the array for a value and returns its index or -1 if not found. Optionally, the initial search index can be passed.
+ Searches the array for a value and returns its index or [code]-1[/code] if not found. Optionally, the initial search index can be passed.
</description>
</method>
<method name="find_last">
@@ -209,7 +209,7 @@
<argument index="0" name="value" type="Variant">
</argument>
<description>
- Searches the array in reverse order for a value and returns its index or -1 if not found.
+ Searches the array in reverse order for a value and returns its index or [code]-1[/code] if not found.
</description>
</method>
<method name="front">
@@ -232,6 +232,12 @@
["inside", 7].has(7) == true
["inside", 7].has("7") == false
[/codeblock]
+ [b]Note:[/b] This is equivalent to using the [code]in[/code] operator as follows:
+ [codeblock]
+ # Will evaluate to `true`.
+ if 2 in [2, 4, 6, 8]:
+ pass
+ [/codeblock]
</description>
</method>
<method name="hash">
diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml
index 4538e5ea4e..5413fa33c6 100644
--- a/doc/classes/Dictionary.xml
+++ b/doc/classes/Dictionary.xml
@@ -126,6 +126,13 @@
</argument>
<description>
Returns [code]true[/code] if the dictionary has a given key.
+ [b]Note:[/b] This is equivalent to using the [code]in[/code] operator as follows:
+ [codeblock]
+ # Will evaluate to `true`.
+ if "godot" in {"godot": "engine"}:
+ pass
+ [/codeblock]
+ This method (like the [code]in[/code] operator) will evaluate to [code]true[/code] as long as the key exists, even if the associated value is [code]null[/code].
</description>
</method>
<method name="has_all">
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 7fe712753c..f8306cbd72 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -127,12 +127,6 @@
<description>
</description>
</method>
- <method name="get_latin_keyboard_variant" qualifiers="const">
- <return type="int" enum="DisplayServer.LatinKeyboardVariant">
- </return>
- <description>
- </description>
- </method>
<method name="get_name" qualifiers="const">
<return type="String">
</return>
@@ -389,6 +383,52 @@
<description>
</description>
</method>
+ <method name="keyboard_get_current_layout" qualifiers="const">
+ <return type="int">
+ </return>
+ <description>
+ Returns active keyboard layout index.
+ [b]Note:[/b] This method is implemented on Linux, macOS and Windows.
+ </description>
+ </method>
+ <method name="keyboard_get_layout_count" qualifiers="const">
+ <return type="int">
+ </return>
+ <description>
+ Returns the number of keyboard layouts.
+ [b]Note:[/b] This method is implemented on Linux, macOS and Windows.
+ </description>
+ </method>
+ <method name="keyboard_get_layout_language" qualifiers="const">
+ <return type="String">
+ </return>
+ <argument index="0" name="index" type="int">
+ </argument>
+ <description>
+ Returns the ISO-639/BCP-47 language code of the keyboard layout at position [code]index[/code].
+ [b]Note:[/b] This method is implemented on Linux, macOS and Windows.
+ </description>
+ </method>
+ <method name="keyboard_get_layout_name" qualifiers="const">
+ <return type="String">
+ </return>
+ <argument index="0" name="index" type="int">
+ </argument>
+ <description>
+ Returns the localized name of the keyboard layout at position [code]index[/code].
+ [b]Note:[/b] This method is implemented on Linux, macOS and Windows.
+ </description>
+ </method>
+ <method name="keyboard_set_current_layout">
+ <return type="void">
+ </return>
+ <argument index="0" name="index" type="int">
+ </argument>
+ <description>
+ Sets active keyboard layout.
+ [b]Note:[/b] This method is implemented on Linux, macOS and Windows.
+ </description>
+ </method>
<method name="mouse_get_absolute_position" qualifiers="const">
<return type="Vector2i">
</return>
@@ -1021,20 +1061,6 @@
</constant>
<constant name="WINDOW_FLAG_MAX" value="5" enum="WindowFlags">
</constant>
- <constant name="LATIN_KEYBOARD_QWERTY" value="0" enum="LatinKeyboardVariant">
- </constant>
- <constant name="LATIN_KEYBOARD_QWERTZ" value="1" enum="LatinKeyboardVariant">
- </constant>
- <constant name="LATIN_KEYBOARD_AZERTY" value="2" enum="LatinKeyboardVariant">
- </constant>
- <constant name="LATIN_KEYBOARD_QZERTY" value="3" enum="LatinKeyboardVariant">
- </constant>
- <constant name="LATIN_KEYBOARD_DVORAK" value="4" enum="LatinKeyboardVariant">
- </constant>
- <constant name="LATIN_KEYBOARD_NEO" value="5" enum="LatinKeyboardVariant">
- </constant>
- <constant name="LATIN_KEYBOARD_COLEMAK" value="6" enum="LatinKeyboardVariant">
- </constant>
<constant name="WINDOW_EVENT_MOUSE_ENTER" value="0" enum="WindowEvent">
</constant>
<constant name="WINDOW_EVENT_MOUSE_EXIT" value="1" enum="WindowEvent">
diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml
index aa8c8d2443..d29fcfd96f 100644
--- a/doc/classes/Image.xml
+++ b/doc/classes/Image.xml
@@ -154,7 +154,7 @@
<argument index="4" name="data" type="PackedByteArray">
</argument>
<description>
- Creates a new image of given size and format. See [enum Format] constants. Fills the image with the given raw data. If [code]use_mipmaps[/code] is [code]true[/code] then generate mipmaps for this image. See the [method generate_mipmaps].
+ Creates a new image of given size and format. See [enum Format] constants. Fills the image with the given raw data. If [code]use_mipmaps[/code] is [code]true[/code] then loads mipmaps for this image from [code]data[/code]. See [method generate_mipmaps].
</description>
</method>
<method name="crop">
diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml
index 263fb6c022..3318f2757d 100644
--- a/doc/classes/Label.xml
+++ b/doc/classes/Label.xml
@@ -57,7 +57,7 @@
</member>
<member name="mouse_filter" type="int" setter="set_mouse_filter" getter="get_mouse_filter" override="true" enum="Control.MouseFilter" default="2" />
<member name="percent_visible" type="float" setter="set_percent_visible" getter="get_percent_visible" default="1.0">
- Limits the count of visible characters. If you set [code]percent_visible[/code] to 50, only up to half of the text's characters will display on screen. Useful to animate the text in a dialog box.
+ Limits the amount of visible characters. If you set [code]percent_visible[/code] to 0.5, only up to half of the text's characters will display on screen. Useful to animate the text in a dialog box.
</member>
<member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" override="true" default="4" />
<member name="text" type="String" setter="set_text" getter="get_text" default="&quot;&quot;">
diff --git a/doc/classes/Material.xml b/doc/classes/Material.xml
index a37c8127f0..f3686876ca 100644
--- a/doc/classes/Material.xml
+++ b/doc/classes/Material.xml
@@ -17,7 +17,7 @@
</member>
<member name="render_priority" type="int" setter="set_render_priority" getter="get_render_priority" default="0">
Sets the render priority for transparent objects in 3D scenes. Higher priority objects will be sorted in front of lower priority objects.
- [b]Note:[/b] this only applies to sorting of transparent objects. This will not impact how transparent objects are sorted relative to opaque objects. This is because opaque objects are sorted based on depth, while transparent objects are sorted from back to front (subject to priority).
+ [b]Note:[/b] this only applies to sorting of transparent objects. This will not impact how transparent objects are sorted relative to opaque objects. This is because opaque objects are not sorted, while transparent objects are sorted from back to front (subject to priority).
</member>
</members>
<constants>
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 04c8d2bf57..9617ee0437 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -55,6 +55,7 @@
It is only called if input processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_process_input].
To consume the input event and stop it propagating further to other nodes, [method Viewport.set_input_as_handled] can be called.
For gameplay input, [method _unhandled_input] and [method _unhandled_key_input] are usually a better fit as they allow the GUI to intercept the events first.
+ [b]Note:[/b] This method is only called if the node is present in the scene tree (i.e. if it's not orphan).
</description>
</method>
<method name="_physics_process" qualifiers="virtual">
@@ -66,6 +67,7 @@
Called during the physics processing step of the main loop. Physics processing means that the frame rate is synced to the physics, i.e. the [code]delta[/code] variable should be constant.
It is only called if physics processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_physics_process].
Corresponds to the [constant NOTIFICATION_PHYSICS_PROCESS] notification in [method Object._notification].
+ [b]Note:[/b] This method is only called if the node is present in the scene tree (i.e. if it's not orphan).
</description>
</method>
<method name="_process" qualifiers="virtual">
@@ -77,6 +79,7 @@
Called during the processing step of the main loop. Processing happens at every frame and as fast as possible, so the [code]delta[/code] time since the previous frame is not constant.
It is only called if processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_process].
Corresponds to the [constant NOTIFICATION_PROCESS] notification in [method Object._notification].
+ [b]Note:[/b] This method is only called if the node is present in the scene tree (i.e. if it's not orphan).
</description>
</method>
<method name="_ready" qualifiers="virtual">
@@ -99,6 +102,7 @@
It is only called if unhandled input processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_process_unhandled_input].
To consume the input event and stop it propagating further to other nodes, [method Viewport.set_input_as_handled] can be called.
For gameplay input, this and [method _unhandled_key_input] are usually a better fit than [method _input] as they allow the GUI to intercept the events first.
+ [b]Note:[/b] This method is only called if the node is present in the scene tree (i.e. if it's not orphan).
</description>
</method>
<method name="_unhandled_key_input" qualifiers="virtual">
@@ -111,6 +115,7 @@
It is only called if unhandled key input processing is enabled, which is done automatically if this method is overridden, and can be toggled with [method set_process_unhandled_key_input].
To consume the input event and stop it propagating further to other nodes, [method Viewport.set_input_as_handled] can be called.
For gameplay input, this and [method _unhandled_input] are usually a better fit than [method _input] as they allow the GUI to intercept the events first.
+ [b]Note:[/b] This method is only called if the node is present in the scene tree (i.e. if it's not orphan).
</description>
</method>
<method name="add_child">
diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml
index 87bcab25db..8d08688b41 100644
--- a/doc/classes/Object.xml
+++ b/doc/classes/Object.xml
@@ -15,6 +15,7 @@
print("position" in n) # Prints "True".
print("other_property" in n) # Prints "False".
[/codeblock]
+ The [code]in[/code] operator will evaluate to [code]true[/code] as long as the key exists, even if the value is [code]null[/code].
Objects also receive notifications. Notifications are a simple way to notify the object about different events, so they can all be handled together. See [method _notification].
</description>
<tutorials>
diff --git a/doc/classes/String.xml b/doc/classes/String.xml
index 0dd6923129..24d92b822a 100644
--- a/doc/classes/String.xml
+++ b/doc/classes/String.xml
@@ -412,7 +412,13 @@
<argument index="1" name="from" type="int" default="0">
</argument>
<description>
- Finds the first occurrence of a substring. Returns the starting position of the substring or -1 if not found. Optionally, the initial search index can be passed.
+ Finds the first occurrence of a substring. Returns the starting position of the substring or [code]-1[/code] if not found. Optionally, the initial search index can be passed.
+ [b]Note:[/b] If you just want to know whether a string contains a substring, use the [code]in[/code] operator as follows:
+ [codeblock]
+ # Will evaluate to `false`.
+ if "i" in "team":
+ pass
+ [/codeblock]
</description>
</method>
<method name="find_last">
@@ -421,7 +427,7 @@
<argument index="0" name="what" type="String">
</argument>
<description>
- Finds the last occurrence of a substring. Returns the starting position of the substring or -1 if not found.
+ Finds the last occurrence of a substring. Returns the starting position of the substring or [code]-1[/code] if not found.
</description>
</method>
<method name="findn">
@@ -432,7 +438,7 @@
<argument index="1" name="from" type="int" default="0">
</argument>
<description>
- Finds the first occurrence of a substring, ignoring case. Returns the starting position of the substring or -1 if not found. Optionally, the initial search index can be passed.
+ Finds the first occurrence of a substring, ignoring case. Returns the starting position of the substring or [code]-1[/code] if not found. Optionally, the initial search index can be passed.
</description>
</method>
<method name="format">
@@ -947,7 +953,7 @@
<argument index="1" name="len" type="int" default="-1">
</argument>
<description>
- Returns part of the string from the position [code]from[/code] with length [code]len[/code]. Argument [code]len[/code] is optional and using -1 will return remaining characters from given position.
+ Returns part of the string from the position [code]from[/code] with length [code]len[/code]. Argument [code]len[/code] is optional and using [code]-1[/code] will return remaining characters from given position.
</description>
</method>
<method name="to_ascii">
diff --git a/doc/classes/SubViewportContainer.xml b/doc/classes/SubViewportContainer.xml
index e6a0bd4866..16d483e7f8 100644
--- a/doc/classes/SubViewportContainer.xml
+++ b/doc/classes/SubViewportContainer.xml
@@ -5,6 +5,7 @@
</brief_description>
<description>
A [Container] node that holds a [SubViewport], automatically setting its size.
+ [b]Note:[/b] Changing a SubViewportContainer's [member Control.rect_scale] will cause its contents to appear distorted. To change its visual size without causing distortion, adjust the node's margins instead (if it's not already in a container).
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml
index c647f83598..9a78e45d46 100644
--- a/doc/classes/TileSet.xml
+++ b/doc/classes/TileSet.xml
@@ -44,6 +44,8 @@
<argument index="1" name="neighbor_id" type="int">
</argument>
<description>
+ Determines when the auto-tiler should consider two different auto-tile IDs to be bound together.
+ [b]Note:[/b] [code]neighbor_id[/code] will be [code]-1[/code] ([constant TileMap.INVALID_CELL]) when checking a tile against an empty neighbor tile.
</description>
</method>
<method name="autotile_clear_bitmask_map">
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 9888013ce6..95c7cc0bf1 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -307,18 +307,19 @@ void FindReplaceBar::_update_results_count() {
break;
}
+ int pos_subsequent = pos + searched.length();
if (is_whole_words()) {
- from_pos++; // Making sure we won't hit the same match next time, if we get out via a continue.
- if (pos > 0 && !is_symbol(full_text[pos - 1])) {
+ from_pos = pos + 1; // Making sure we won't hit the same match next time, if we get out via a continue.
+ if (pos > 0 && !(is_symbol(full_text[pos - 1]) || full_text[pos - 1] == '\n')) {
continue;
}
- if (pos + searched.length() < full_text.length() && !is_symbol(full_text[pos + searched.length()])) {
+ if (pos_subsequent < full_text.length() && !(is_symbol(full_text[pos_subsequent]) || full_text[pos_subsequent] == '\n')) {
continue;
}
}
results_count++;
- from_pos = pos + searched.length();
+ from_pos = pos_subsequent;
}
}
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index e4a853dbd7..73468f8ce0 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -347,20 +347,22 @@ void CreateDialog::_update_search() {
} else {
bool found = false;
String type2 = type;
+ bool cpp_type2 = cpp_type;
if (!cpp_type && !search_loaded_scripts.has(type)) {
search_loaded_scripts[type] = ed.script_class_load_script(type);
}
- while (type2 != "" && (cpp_type ? ClassDB::is_parent_class(type2, base_type) : ed.script_class_is_parent(type2, base_type)) && type2 != base_type) {
+ while (type2 != "" && (cpp_type2 ? ClassDB::is_parent_class(type2, base_type) : ed.script_class_is_parent(type2, base_type)) && type2 != base_type) {
if (search_box->get_text().is_subsequence_ofi(type2)) {
found = true;
break;
}
- type2 = cpp_type ? ClassDB::get_parent_class(type2) : ed.script_class_get_base(type2);
+ type2 = cpp_type2 ? ClassDB::get_parent_class(type2) : ed.script_class_get_base(type2);
+ cpp_type2 = cpp_type2 || ClassDB::class_exists(type2); // Built-in class can't inherit from custom type, so we can skip the check if it's already true.
- if (!cpp_type && !search_loaded_scripts.has(type2)) {
+ if (!cpp_type2 && !search_loaded_scripts.has(type2)) {
search_loaded_scripts[type2] = ed.script_class_load_script(type2);
}
}
@@ -511,30 +513,45 @@ String CreateDialog::get_selected_type() {
Object *CreateDialog::instance_selected() {
TreeItem *selected = search_options->get_selected();
- if (selected) {
- Variant md = selected->get_metadata(0);
+ if (!selected) {
+ return nullptr;
+ }
- String custom;
- if (md.get_type() != Variant::NIL) {
- custom = md;
- }
+ Variant md = selected->get_metadata(0);
+ String custom;
+ if (md.get_type() != Variant::NIL) {
+ custom = md;
+ }
- if (custom != String()) {
- if (ScriptServer::is_global_class(custom)) {
- Object *obj = EditorNode::get_editor_data().script_class_instance(custom);
- Node *n = Object::cast_to<Node>(obj);
- if (n) {
- n->set_name(custom);
- }
- return obj;
+ Object *obj = nullptr;
+
+ if (!custom.empty()) {
+ if (ScriptServer::is_global_class(custom)) {
+ obj = EditorNode::get_editor_data().script_class_instance(custom);
+ Node *n = Object::cast_to<Node>(obj);
+ if (n) {
+ n->set_name(custom);
}
- return EditorNode::get_editor_data().instance_custom_type(selected->get_text(0), custom);
+ obj = n;
} else {
- return ClassDB::instance(selected->get_text(0));
+ obj = EditorNode::get_editor_data().instance_custom_type(selected->get_text(0), custom);
+ }
+ } else {
+ obj = ClassDB::instance(selected->get_text(0));
+ }
+
+ // Check if any Object-type property should be instantiated.
+ List<PropertyInfo> pinfo;
+ obj->get_property_list(&pinfo);
+
+ for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
+ if (E->get().type == Variant::OBJECT && E->get().usage & PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT) {
+ Object *prop = ClassDB::instance(E->get().class_name);
+ obj->set(E->get().name, prop);
}
}
- return nullptr;
+ return obj;
}
void CreateDialog::_item_selected() {
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index f8dec13a5c..5cf5201b18 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -545,6 +545,17 @@ void EditorAudioBus::_gui_input(const Ref<InputEvent> &p_event) {
}
}
+void EditorAudioBus::_unhandled_key_input(Ref<InputEvent> p_event) {
+ Ref<InputEventKey> k = p_event;
+ if (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == KEY_DELETE) {
+ TreeItem *current_effect = effects->get_selected();
+ if (current_effect && current_effect->get_metadata(0).get_type() == Variant::INT) {
+ _delete_effect_pressed(0);
+ accept_event();
+ }
+ }
+}
+
void EditorAudioBus::_bus_popup_pressed(int p_option) {
if (p_option == 2) {
// Reset volume
@@ -738,6 +749,7 @@ void EditorAudioBus::_bind_methods() {
ClassDB::bind_method("update_bus", &EditorAudioBus::update_bus);
ClassDB::bind_method("update_send", &EditorAudioBus::update_send);
ClassDB::bind_method("_gui_input", &EditorAudioBus::_gui_input);
+ ClassDB::bind_method("_unhandled_key_input", &EditorAudioBus::_unhandled_key_input);
ClassDB::bind_method("get_drag_data_fw", &EditorAudioBus::get_drag_data_fw);
ClassDB::bind_method("can_drop_data_fw", &EditorAudioBus::can_drop_data_fw);
ClassDB::bind_method("drop_data_fw", &EditorAudioBus::drop_data_fw);
@@ -761,6 +773,7 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) {
add_child(vb);
set_v_size_flags(SIZE_EXPAND_FILL);
+ set_process_unhandled_key_input(true);
track_name = memnew(LineEdit);
track_name->connect("text_entered", callable_mp(this, &EditorAudioBus::_name_changed));
diff --git a/editor/editor_audio_buses.h b/editor/editor_audio_buses.h
index 6b2d9e4436..65caf84f0f 100644
--- a/editor/editor_audio_buses.h
+++ b/editor/editor_audio_buses.h
@@ -92,6 +92,7 @@ class EditorAudioBus : public PanelContainer {
mutable bool hovering_drop;
void _gui_input(const Ref<InputEvent> &p_event);
+ void _unhandled_key_input(Ref<InputEvent> p_event);
void _bus_popup_pressed(int p_option);
void _name_changed(const String &p_new_name);
diff --git a/editor/import/editor_scene_importer_gltf.cpp b/editor/import/editor_scene_importer_gltf.cpp
index e340f41e3b..1a232658fe 100644
--- a/editor/import/editor_scene_importer_gltf.cpp
+++ b/editor/import/editor_scene_importer_gltf.cpp
@@ -286,7 +286,16 @@ Error EditorSceneImporterGLTF::_parse_nodes(GLTFState &state) {
node->xform.basis.set_quat_scale(node->rotation, node->scale);
node->xform.origin = node->translation;
}
-
+ if (n.has("extensions")) {
+ Dictionary extensions = n["extensions"];
+ if (extensions.has("KHR_lights_punctual")) {
+ Dictionary lights_punctual = extensions["KHR_lights_punctual"];
+ if (lights_punctual.has("light")) {
+ GLTFLightIndex light = lights_punctual["light"];
+ node->light = light;
+ }
+ }
+ }
if (n.has("children")) {
const Array &children = n["children"];
for (int j = 0; j < children.size(); j++) {
@@ -2245,6 +2254,58 @@ void EditorSceneImporterGLTF::_remove_duplicate_skins(GLTFState &state) {
}
}
+Error EditorSceneImporterGLTF::_parse_lights(GLTFState &state) {
+ if (!state.json.has("extensions")) {
+ return OK;
+ }
+ Dictionary extensions = state.json["extensions"];
+ if (!extensions.has("KHR_lights_punctual")) {
+ return OK;
+ }
+ Dictionary lights_punctual = extensions["KHR_lights_punctual"];
+ if (!lights_punctual.has("lights")) {
+ return OK;
+ }
+
+ const Array &lights = lights_punctual["lights"];
+
+ for (GLTFLightIndex light_i = 0; light_i < lights.size(); light_i++) {
+ const Dictionary &d = lights[light_i];
+
+ GLTFLight light;
+ ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR);
+ const String &type = d["type"];
+ light.type = type;
+
+ if (d.has("color")) {
+ const Array &arr = d["color"];
+ ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR);
+ const Color c = Color(arr[0], arr[1], arr[2]).to_srgb();
+ light.color = c;
+ }
+ if (d.has("intensity")) {
+ light.intensity = d["intensity"];
+ }
+ if (d.has("range")) {
+ light.range = d["range"];
+ }
+ if (type == "spot") {
+ const Dictionary &spot = d["spot"];
+ light.inner_cone_angle = spot["innerConeAngle"];
+ light.outer_cone_angle = spot["outerConeAngle"];
+ ERR_FAIL_COND_V_MSG(light.inner_cone_angle >= light.outer_cone_angle, ERR_PARSE_ERROR, "The inner angle must be smaller than the outer angle.");
+ } else if (type != "point" && type != "directional") {
+ ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Light type is unknown.");
+ }
+
+ state.lights.push_back(light);
+ }
+
+ print_verbose("glTF: Total lights: " + itos(state.lights.size()));
+
+ return OK;
+}
+
Error EditorSceneImporterGLTF::_parse_cameras(GLTFState &state) {
if (!state.json.has("cameras")) {
return OK;
@@ -2488,6 +2549,58 @@ MeshInstance3D *EditorSceneImporterGLTF::_generate_mesh_instance(GLTFState &stat
return mi;
}
+Light3D *EditorSceneImporterGLTF::_generate_light(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) {
+ const GLTFNode *gltf_node = state.nodes[node_index];
+
+ ERR_FAIL_INDEX_V(gltf_node->light, state.lights.size(), nullptr);
+
+ print_verbose("glTF: Creating light for: " + gltf_node->name);
+
+ const GLTFLight &l = state.lights[gltf_node->light];
+
+ float intensity = l.intensity;
+ if (intensity > 10) {
+ // GLTF spec has the default around 1, but Blender defaults lights to 100.
+ // The only sane way to handle this is to check where it came from and
+ // handle it accordingly. If it's over 10, it probably came from Blender.
+ intensity /= 100;
+ }
+
+ if (l.type == "directional") {
+ DirectionalLight3D *light = memnew(DirectionalLight3D);
+ light->set_param(Light3D::PARAM_ENERGY, intensity);
+ light->set_color(l.color);
+ return light;
+ }
+
+ const float range = CLAMP(l.range, 0, 4096);
+ // Doubling the range will double the effective brightness, so we need double attenuation (half brightness).
+ // We want to have double intensity give double brightness, so we need half the attenuation.
+ const float attenuation = range / intensity;
+ if (l.type == "point") {
+ OmniLight3D *light = memnew(OmniLight3D);
+ light->set_param(OmniLight3D::PARAM_ATTENUATION, attenuation);
+ light->set_param(OmniLight3D::PARAM_RANGE, range);
+ light->set_color(l.color);
+ return light;
+ }
+ if (l.type == "spot") {
+ SpotLight3D *light = memnew(SpotLight3D);
+ light->set_param(SpotLight3D::PARAM_ATTENUATION, attenuation);
+ light->set_param(SpotLight3D::PARAM_RANGE, range);
+ light->set_param(SpotLight3D::PARAM_SPOT_ANGLE, Math::rad2deg(l.outer_cone_angle));
+ light->set_color(l.color);
+
+ // Line of best fit derived from guessing, see https://www.desmos.com/calculator/biiflubp8b
+ // The points in desmos are not exact, except for (1, infinity).
+ float angle_ratio = l.inner_cone_angle / l.outer_cone_angle;
+ float angle_attenuation = 0.2 / (1 - angle_ratio) - 0.1;
+ light->set_param(SpotLight3D::PARAM_SPOT_ATTENUATION, angle_attenuation);
+ return light;
+ }
+ return nullptr;
+}
+
Camera3D *EditorSceneImporterGLTF::_generate_camera(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index) {
const GLTFNode *gltf_node = state.nodes[node_index];
@@ -2561,6 +2674,8 @@ void EditorSceneImporterGLTF::_generate_scene_node(GLTFState &state, Node *scene
current_node = _generate_mesh_instance(state, scene_parent, node_index);
} else if (gltf_node->camera >= 0) {
current_node = _generate_camera(state, scene_parent, node_index);
+ } else if (gltf_node->light >= 0) {
+ current_node = _generate_light(state, scene_parent, node_index);
} else {
current_node = _generate_spatial(state, scene_parent, node_index);
}
@@ -3037,22 +3152,28 @@ Node *EditorSceneImporterGLTF::import_scene(const String &p_path, uint32_t p_fla
return nullptr;
}
- /* STEP 14 PARSE CAMERAS */
+ /* STEP 14 PARSE LIGHTS */
+ err = _parse_lights(state);
+ if (err != OK) {
+ return NULL;
+ }
+
+ /* STEP 15 PARSE CAMERAS */
err = _parse_cameras(state);
if (err != OK) {
return nullptr;
}
- /* STEP 15 PARSE ANIMATIONS */
+ /* STEP 16 PARSE ANIMATIONS */
err = _parse_animations(state);
if (err != OK) {
return nullptr;
}
- /* STEP 16 ASSIGN SCENE NAMES */
+ /* STEP 17 ASSIGN SCENE NAMES */
_assign_scene_names(state);
- /* STEP 17 MAKE SCENE! */
+ /* STEP 18 MAKE SCENE! */
Node3D *scene = _generate_scene(state, p_bake_fps);
return scene;
diff --git a/editor/import/editor_scene_importer_gltf.h b/editor/import/editor_scene_importer_gltf.h
index d45410fa57..95c6b87af5 100644
--- a/editor/import/editor_scene_importer_gltf.h
+++ b/editor/import/editor_scene_importer_gltf.h
@@ -32,6 +32,7 @@
#define EDITOR_SCENE_IMPORTER_GLTF_H
#include "editor/import/resource_importer_scene.h"
+#include "scene/3d/light_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
@@ -50,6 +51,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter {
typedef int GLTFImageIndex;
typedef int GLTFMaterialIndex;
typedef int GLTFMeshIndex;
+ typedef int GLTFLightIndex;
typedef int GLTFNodeIndex;
typedef int GLTFSkeletonIndex;
typedef int GLTFSkinIndex;
@@ -113,6 +115,8 @@ class EditorSceneImporterGLTF : public EditorSceneImporter {
GLTFNodeIndex fake_joint_parent = -1;
+ GLTFLightIndex light = -1;
+
GLTFNode() {}
};
@@ -218,6 +222,17 @@ class EditorSceneImporterGLTF : public EditorSceneImporter {
GLTFCamera() {}
};
+ struct GLTFLight {
+ Color color = Color(1.0f, 1.0f, 1.0f);
+ float intensity = 1.0f;
+ String type = "";
+ float range = Math_INF;
+ float inner_cone_angle = 0.0f;
+ float outer_cone_angle = Math_PI / 4.0;
+
+ GLTFLight() {}
+ };
+
struct GLTFAnimation {
bool loop = false;
@@ -271,6 +286,7 @@ class EditorSceneImporterGLTF : public EditorSceneImporter {
Vector<GLTFSkin> skins;
Vector<GLTFCamera> cameras;
+ Vector<GLTFLight> lights;
Set<String> unique_names;
@@ -349,12 +365,13 @@ class EditorSceneImporterGLTF : public EditorSceneImporter {
void _remove_duplicate_skins(GLTFState &state);
Error _parse_cameras(GLTFState &state);
-
+ Error _parse_lights(GLTFState &state);
Error _parse_animations(GLTFState &state);
BoneAttachment3D *_generate_bone_attachment(GLTFState &state, Skeleton3D *skeleton, const GLTFNodeIndex node_index);
MeshInstance3D *_generate_mesh_instance(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index);
Camera3D *_generate_camera(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index);
+ Light3D *_generate_light(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index);
Node3D *_generate_spatial(GLTFState &state, Node *scene_parent, const GLTFNodeIndex node_index);
void _generate_scene_node(GLTFState &state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index);
diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp
index a13324f0fc..3a0e624a8f 100644
--- a/editor/import/resource_importer_texture.cpp
+++ b/editor/import/resource_importer_texture.cpp
@@ -181,15 +181,14 @@ bool ResourceImporterTexture::get_option_visibility(const String &p_option, cons
}
int ResourceImporterTexture::get_preset_count() const {
- return 4;
+ return 3;
}
String ResourceImporterTexture::get_preset_name(int p_idx) const {
static const char *preset_names[] = {
- "2D, Detect 3D",
+ "2D/3D (Auto-Detect)",
"2D",
- "2D Pixel",
- "3D"
+ "3D",
};
return preset_names[p_idx];
diff --git a/editor/import/resource_importer_texture.h b/editor/import/resource_importer_texture.h
index b770d240eb..12eb7f67c2 100644
--- a/editor/import/resource_importer_texture.h
+++ b/editor/import/resource_importer_texture.h
@@ -93,7 +93,6 @@ public:
enum Preset {
PRESET_DETECT,
PRESET_2D,
- PRESET_2D_PIXEL,
PRESET_3D,
};
diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp
index 321b4432ab..2586f17fe1 100644
--- a/editor/plugins/skeleton_3d_editor_plugin.cpp
+++ b/editor/plugins/skeleton_3d_editor_plugin.cpp
@@ -665,7 +665,6 @@ void Skeleton3DEditor::create_editors() {
options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON);
options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_option));
- options->hide();
const Color section_color = get_theme_color("prop_subsection", "Editor");
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index 499f7d4017..cbba4b4834 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -1741,10 +1741,6 @@ void ProjectList::_panel_input(const Ref<InputEvent> &p_ev, Node *p_hb) {
select_project(clicked_index);
}
- if (_selected_project_keys.has(clicked_project.project_key)) {
- clicked_project.control->grab_focus();
- }
-
emit_signal(SIGNAL_SELECTION_CHANGED);
if (!mb->get_control() && mb->is_doubleclick()) {
diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml
index 3130c53331..c00fa96b2e 100644
--- a/modules/regex/doc_classes/RegEx.xml
+++ b/modules/regex/doc_classes/RegEx.xml
@@ -32,8 +32,7 @@
[codeblock]
for result in regex.search_all("d01, d03, d0c, x3f and x42"):
print(result.get_string("digit"))
- # Would print 01 03 3f 42
- # Note that d0c would not match
+ # Would print 01 03 0 3f 42
[/codeblock]
[b]Note:[/b] Godot's regex implementation is based on the [url=https://www.pcre.org/]PCRE2[/url] library. You can view the full pattern reference [url=https://www.pcre.org/current/doc/html/pcre2pattern.html]here[/url].
[b]Tip:[/b] You can use [url=https://regexr.com/]Regexr[/url] to test regular expressions online.
diff --git a/modules/regex/doc_classes/RegExMatch.xml b/modules/regex/doc_classes/RegExMatch.xml
index 151e881b6f..a45de60aef 100644
--- a/modules/regex/doc_classes/RegExMatch.xml
+++ b/modules/regex/doc_classes/RegExMatch.xml
@@ -49,7 +49,7 @@
</methods>
<members>
<member name="names" type="Dictionary" setter="" getter="get_names" default="{}">
- A dictionary of named groups and its corresponding group number. Only groups with that were matched are included. If multiple groups have the same name, that name would refer to the first matching one.
+ A dictionary of named groups and its corresponding group number. Only groups that were matched are included. If multiple groups have the same name, that name would refer to the first matching one.
</member>
<member name="strings" type="Array" setter="" getter="get_strings" default="[ ]">
An [Array] of the match and its capturing groups.
diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp
index 740a72fafa..fd61c46e63 100644
--- a/platform/javascript/javascript_main.cpp
+++ b/platform/javascript/javascript_main.cpp
@@ -80,6 +80,9 @@ extern "C" EMSCRIPTEN_KEEPALIVE void main_after_fs_sync(char *p_idbfs_err) {
Main::start();
os->get_main_loop()->init();
emscripten_resume_main_loop();
+ // Immediately run the first iteration.
+ // We are inside an animation frame, we want to immediately draw on the newly setup canvas.
+ main_loop_callback();
}
int main(int argc, char *argv[]) {
@@ -91,14 +94,15 @@ int main(int argc, char *argv[]) {
// Sync from persistent state into memory and then
// run the 'main_after_fs_sync' function.
/* clang-format off */
- EM_ASM(
+ EM_ASM({
FS.mkdir('/userfs');
FS.mount(IDBFS, {}, '/userfs');
FS.syncfs(true, function(err) {
- ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""])
+ requestAnimationFrame(function() {
+ ccall('main_after_fs_sync', null, ['string'], [err ? err.message : ""]);
+ });
});
-
- );
+ });
/* clang-format on */
return 0;
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index ef5ac66b34..a0954600a2 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -1846,37 +1846,106 @@ void DisplayServerX11::cursor_set_custom_image(const RES &p_cursor, CursorShape
}
}
-DisplayServerX11::LatinKeyboardVariant DisplayServerX11::get_latin_keyboard_variant() const {
- _THREAD_SAFE_METHOD_
-
- XkbDescRec *xkbdesc = XkbAllocKeyboard();
- ERR_FAIL_COND_V(!xkbdesc, LATIN_KEYBOARD_QWERTY);
+int DisplayServerX11::keyboard_get_layout_count() const {
+ int _group_count = 0;
+ XkbDescRec *kbd = XkbAllocKeyboard();
+ if (kbd) {
+ kbd->dpy = x11_display;
+ XkbGetControls(x11_display, XkbAllControlsMask, kbd);
+ XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
+
+ const Atom *groups = kbd->names->groups;
+ if (kbd->ctrls != NULL) {
+ _group_count = kbd->ctrls->num_groups;
+ } else {
+ while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
+ _group_count++;
+ }
+ }
+ XkbFreeKeyboard(kbd, 0, true);
+ }
+ return _group_count;
+}
- XkbGetNames(x11_display, XkbSymbolsNameMask, xkbdesc);
- ERR_FAIL_COND_V(!xkbdesc->names, LATIN_KEYBOARD_QWERTY);
- ERR_FAIL_COND_V(!xkbdesc->names->symbols, LATIN_KEYBOARD_QWERTY);
+int DisplayServerX11::keyboard_get_current_layout() const {
+ XkbStateRec state;
+ XkbGetState(x11_display, XkbUseCoreKbd, &state);
+ return state.group;
+}
- char *layout = XGetAtomName(x11_display, xkbdesc->names->symbols);
- ERR_FAIL_COND_V(!layout, LATIN_KEYBOARD_QWERTY);
+void DisplayServerX11::keyboard_set_current_layout(int p_index) {
+ ERR_FAIL_INDEX(p_index, keyboard_get_layout_count());
+ XkbLockGroup(x11_display, XkbUseCoreKbd, p_index);
+}
- Vector<String> info = String(layout).split("+");
- ERR_FAIL_INDEX_V(1, info.size(), LATIN_KEYBOARD_QWERTY);
+String DisplayServerX11::keyboard_get_layout_language(int p_index) const {
+ String ret;
+ XkbDescRec *kbd = XkbAllocKeyboard();
+ if (kbd) {
+ kbd->dpy = x11_display;
+ XkbGetControls(x11_display, XkbAllControlsMask, kbd);
+ XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
+ XkbGetNames(x11_display, XkbGroupNamesMask, kbd);
+
+ int _group_count = 0;
+ const Atom *groups = kbd->names->groups;
+ if (kbd->ctrls != NULL) {
+ _group_count = kbd->ctrls->num_groups;
+ } else {
+ while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
+ _group_count++;
+ }
+ }
- if (info[1].find("colemak") != -1) {
- return LATIN_KEYBOARD_COLEMAK;
- } else if (info[1].find("qwertz") != -1) {
- return LATIN_KEYBOARD_QWERTZ;
- } else if (info[1].find("azerty") != -1) {
- return LATIN_KEYBOARD_AZERTY;
- } else if (info[1].find("qzerty") != -1) {
- return LATIN_KEYBOARD_QZERTY;
- } else if (info[1].find("dvorak") != -1) {
- return LATIN_KEYBOARD_DVORAK;
- } else if (info[1].find("neo") != -1) {
- return LATIN_KEYBOARD_NEO;
+ Atom names = kbd->names->symbols;
+ if (names != None) {
+ char *name = XGetAtomName(x11_display, names);
+ Vector<String> info = String(name).split("+");
+ if (p_index >= 0 && p_index < _group_count) {
+ if (p_index + 1 < info.size()) {
+ ret = info[p_index + 1]; // Skip "pc" at the start and "inet"/"group" at the end of symbols.
+ } else {
+ ret = "en"; // No symbol for layout fallback to "en".
+ }
+ } else {
+ ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");
+ }
+ XFree(name);
+ }
+ XkbFreeKeyboard(kbd, 0, true);
}
+ return ret.substr(0, 2);
+}
+
+String DisplayServerX11::keyboard_get_layout_name(int p_index) const {
+ String ret;
+ XkbDescRec *kbd = XkbAllocKeyboard();
+ if (kbd) {
+ kbd->dpy = x11_display;
+ XkbGetControls(x11_display, XkbAllControlsMask, kbd);
+ XkbGetNames(x11_display, XkbSymbolsNameMask, kbd);
+ XkbGetNames(x11_display, XkbGroupNamesMask, kbd);
+
+ int _group_count = 0;
+ const Atom *groups = kbd->names->groups;
+ if (kbd->ctrls != NULL) {
+ _group_count = kbd->ctrls->num_groups;
+ } else {
+ while (_group_count < XkbNumKbdGroups && groups[_group_count] != None) {
+ _group_count++;
+ }
+ }
- return LATIN_KEYBOARD_QWERTY;
+ if (p_index >= 0 && p_index < _group_count) {
+ char *full_name = XGetAtomName(x11_display, groups[p_index]);
+ ret.parse_utf8(full_name);
+ XFree(full_name);
+ } else {
+ ERR_PRINT("Index " + itos(p_index) + "is out of bounds (" + itos(_group_count) + ").");
+ }
+ XkbFreeKeyboard(kbd, 0, true);
+ }
+ return ret;
}
DisplayServerX11::Property DisplayServerX11::_read_property(Display *p_display, Window p_window, Atom p_property) {
diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h
index b5ea71f72a..f01b9a2323 100644
--- a/platform/linuxbsd/display_server_x11.h
+++ b/platform/linuxbsd/display_server_x11.h
@@ -327,7 +327,11 @@ public:
virtual CursorShape cursor_get_shape() const;
virtual void cursor_set_custom_image(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot);
- virtual LatinKeyboardVariant get_latin_keyboard_variant() const;
+ virtual int keyboard_get_layout_count() const;
+ virtual int keyboard_get_current_layout() const;
+ virtual void keyboard_set_current_layout(int p_index);
+ virtual String keyboard_get_layout_language(int p_index) const;
+ virtual String keyboard_get_layout_name(int p_index) const;
virtual void process_events();
diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h
index 8133dfe2c4..fddb1d0ca6 100644
--- a/platform/osx/display_server_osx.h
+++ b/platform/osx/display_server_osx.h
@@ -281,7 +281,11 @@ public:
virtual bool get_swap_ok_cancel();
- virtual LatinKeyboardVariant get_latin_keyboard_variant() const;
+ virtual int keyboard_get_layout_count() const;
+ virtual int keyboard_get_current_layout() const;
+ virtual void keyboard_set_current_layout(int p_index);
+ virtual String keyboard_get_layout_language(int p_index) const;
+ virtual String keyboard_get_layout_name(int p_index) const;
virtual void process_events();
virtual void force_process_and_drop_events();
diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm
index 9a1191490c..4a94e09c1c 100644
--- a/platform/osx/display_server_osx.mm
+++ b/platform/osx/display_server_osx.mm
@@ -2987,85 +2987,129 @@ void DisplayServerOSX::cursor_set_custom_image(const RES &p_cursor, CursorShape
}
}
+struct LayoutInfo {
+ String name;
+ String code;
+};
+
+static Vector<LayoutInfo> kbd_layouts;
+static int current_layout = 0;
static bool keyboard_layout_dirty = true;
static void keyboard_layout_changed(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef user_info) {
+ kbd_layouts.clear();
+ current_layout = 0;
keyboard_layout_dirty = true;
}
-// Returns string representation of keys, if they are printable.
-static NSString *createStringForKeys(const CGKeyCode *keyCode, int length) {
- TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
- if (!currentKeyboard)
- return nil;
+void _update_keyboard_layouts() {
+ @autoreleasepool {
+ TISInputSourceRef cur_source = TISCopyCurrentKeyboardInputSource();
+ NSString *cur_name = (NSString *)TISGetInputSourceProperty(cur_source, kTISPropertyLocalizedName);
+ CFRelease(cur_source);
- CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
- if (!layoutData)
- return nil;
+ // Enum IME layouts
+ NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode };
+ NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false);
+ for (NSUInteger i = 0; i < [list_ime count]; i++) {
+ LayoutInfo ly;
+ NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName);
+ ly.name.parse_utf8([name UTF8String]);
- const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);
+ NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyInputSourceLanguages);
+ ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]);
+ kbd_layouts.push_back(ly);
- OSStatus err;
- CFMutableStringRef output = CFStringCreateMutable(NULL, 0);
+ if ([name isEqualToString:cur_name]) {
+ current_layout = kbd_layouts.size() - 1;
+ }
+ }
+ [list_ime release];
- for (int i = 0; i < length; ++i) {
- UInt32 keysDown = 0;
- UniChar chars[4];
- UniCharCount realLength;
+ // Enum plain keyboard layouts
+ NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout };
+ NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false);
+ for (NSUInteger i = 0; i < [list_kbd count]; i++) {
+ LayoutInfo ly;
+ NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName);
+ ly.name.parse_utf8([name UTF8String]);
- err = UCKeyTranslate(keyboardLayout,
- keyCode[i],
- kUCKeyActionDisplay,
- 0,
- LMGetKbdType(),
- kUCKeyTranslateNoDeadKeysBit,
- &keysDown,
- sizeof(chars) / sizeof(chars[0]),
- &realLength,
- chars);
+ NSArray *langs = (NSArray *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyInputSourceLanguages);
+ ly.code.parse_utf8([(NSString *)[langs objectAtIndex:0] UTF8String]);
+ kbd_layouts.push_back(ly);
- if (err != noErr) {
- CFRelease(output);
- return nil;
+ if ([name isEqualToString:cur_name]) {
+ current_layout = kbd_layouts.size() - 1;
+ }
}
-
- CFStringAppendCharacters(output, chars, 1);
+ [list_kbd release];
}
- return (NSString *)output;
+ keyboard_layout_dirty = false;
}
-DisplayServerOSX::LatinKeyboardVariant DisplayServerOSX::get_latin_keyboard_variant() const {
- _THREAD_SAFE_METHOD_
-
- static LatinKeyboardVariant layout = LATIN_KEYBOARD_QWERTY;
+int DisplayServerOSX::keyboard_get_layout_count() const {
+ if (keyboard_layout_dirty) {
+ _update_keyboard_layouts();
+ }
+ return kbd_layouts.size();
+}
+void DisplayServerOSX::keyboard_set_current_layout(int p_index) {
if (keyboard_layout_dirty) {
- layout = LATIN_KEYBOARD_QWERTY;
+ _update_keyboard_layouts();
+ }
+
+ ERR_FAIL_INDEX(p_index, kbd_layouts.size());
- CGKeyCode keys[] = { kVK_ANSI_Q, kVK_ANSI_W, kVK_ANSI_E, kVK_ANSI_R, kVK_ANSI_T, kVK_ANSI_Y };
- NSString *test = createStringForKeys(keys, 6);
+ NSString *cur_name = [NSString stringWithUTF8String:kbd_layouts[p_index].name.utf8().get_data()];
- if ([test isEqualToString:@"qwertz"]) {
- layout = LATIN_KEYBOARD_QWERTZ;
- } else if ([test isEqualToString:@"azerty"]) {
- layout = LATIN_KEYBOARD_AZERTY;
- } else if ([test isEqualToString:@"qzerty"]) {
- layout = LATIN_KEYBOARD_QZERTY;
- } else if ([test isEqualToString:@"',.pyf"]) {
- layout = LATIN_KEYBOARD_DVORAK;
- } else if ([test isEqualToString:@"xvlcwk"]) {
- layout = LATIN_KEYBOARD_NEO;
- } else if ([test isEqualToString:@"qwfpgj"]) {
- layout = LATIN_KEYBOARD_COLEMAK;
+ NSDictionary *filter_kbd = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardLayout };
+ NSArray *list_kbd = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_kbd, false);
+ for (NSUInteger i = 0; i < [list_kbd count]; i++) {
+ NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_kbd objectAtIndex:i], kTISPropertyLocalizedName);
+ if ([name isEqualToString:cur_name]) {
+ TISSelectInputSource((TISInputSourceRef)[list_kbd objectAtIndex:i]);
+ break;
}
+ }
+ [list_kbd release];
- [test release];
+ NSDictionary *filter_ime = @{ (NSString *)kTISPropertyInputSourceType : (NSString *)kTISTypeKeyboardInputMode };
+ NSArray *list_ime = (NSArray *)TISCreateInputSourceList((CFDictionaryRef)filter_ime, false);
+ for (NSUInteger i = 0; i < [list_ime count]; i++) {
+ NSString *name = (NSString *)TISGetInputSourceProperty((TISInputSourceRef)[list_ime objectAtIndex:i], kTISPropertyLocalizedName);
+ if ([name isEqualToString:cur_name]) {
+ TISSelectInputSource((TISInputSourceRef)[list_ime objectAtIndex:i]);
+ break;
+ }
+ }
+ [list_ime release];
+}
+
+int DisplayServerOSX::keyboard_get_current_layout() const {
+ if (keyboard_layout_dirty) {
+ _update_keyboard_layouts();
+ }
- keyboard_layout_dirty = false;
- return layout;
+ return current_layout;
+}
+
+String DisplayServerOSX::keyboard_get_layout_language(int p_index) const {
+ if (keyboard_layout_dirty) {
+ _update_keyboard_layouts();
+ }
+
+ ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), "");
+ return kbd_layouts[p_index].code;
+}
+
+String DisplayServerOSX::keyboard_get_layout_name(int p_index) const {
+ if (keyboard_layout_dirty) {
+ _update_keyboard_layouts();
}
- return layout;
+ ERR_FAIL_INDEX_V(p_index, kbd_layouts.size(), "");
+ return kbd_layouts[p_index].name;
}
void DisplayServerOSX::_push_input(const Ref<InputEvent> &p_event) {
diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp
index 784fba75ec..c9b01ebbb4 100644
--- a/platform/osx/export/export.cpp
+++ b/platform/osx/export/export.cpp
@@ -55,6 +55,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
void _fix_plist(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &plist, const String &p_binary);
void _make_icon(const Ref<Image> &p_icon, Vector<uint8_t> &p_data);
+ Error _notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path);
Error _create_dmg(const String &p_dmg_path, const String &p_pkg_name, const String &p_app_path_name);
void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name);
@@ -66,6 +67,28 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
bool use_codesign() const { return false; }
bool use_dmg() const { return false; }
#endif
+ bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const {
+ String pname = p_package;
+
+ if (pname.length() == 0) {
+ if (r_error) {
+ *r_error = TTR("Identifier is missing.");
+ }
+ return false;
+ }
+
+ for (int i = 0; i < pname.length(); i++) {
+ CharType c = pname[i];
+ if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
+ if (r_error) {
+ *r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
+ }
+ return false;
+ }
+ }
+
+ return true;
+ }
protected:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features);
@@ -138,6 +161,11 @@ void EditorExportPlatformOSX::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "codesign/hardened_runtime"), true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/entitlements", PROPERTY_HINT_GLOBAL_FILE, "*.plist"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
+
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "notarization/enable"), false));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PLACEHOLDER_TEXT, "Enable two-factor authentication and provide app-specific password"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), ""));
#endif
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), true));
@@ -363,6 +391,52 @@ void EditorExportPlatformOSX::_fix_plist(const Ref<EditorExportPreset> &p_preset
- and then wrap it up in a DMG
**/
+Error EditorExportPlatformOSX::_notarize(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
+#ifdef OSX_ENABLED
+ List<String> args;
+
+ args.push_back("altool");
+ args.push_back("--notarize-app");
+
+ args.push_back("--primary-bundle-id");
+ args.push_back(p_preset->get("application/identifier"));
+
+ args.push_back("--username");
+ args.push_back(p_preset->get("notarization/apple_id_name"));
+
+ args.push_back("--password");
+ args.push_back(p_preset->get("notarization/apple_id_password"));
+
+ args.push_back("--type");
+ args.push_back("osx");
+
+ if (p_preset->get("notarization/apple_team_id")) {
+ args.push_back("--asc-provider");
+ args.push_back(p_preset->get("notarization/apple_team_id"));
+ }
+
+ args.push_back("--file");
+ args.push_back(p_path);
+
+ String str;
+ Error err = OS::get_singleton()->execute("xcrun", args, true, nullptr, &str, nullptr, true);
+ ERR_FAIL_COND_V(err != OK, err);
+
+ print_line("altool (" + p_path + "):\n" + str);
+ if (str.find("RequestUUID") == -1) {
+ EditorNode::add_io_error("altool: " + str);
+ return FAILED;
+ } else {
+ print_line("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.");
+ print_line(" You can check progress manually by opening a Terminal and running the following command:");
+ print_line(" \"xcrun altool --notarization-history 0 -u <your email> -p <app-specific pwd>\"");
+ }
+
+#endif
+
+ return OK;
+}
+
Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
#ifdef OSX_ENABLED
List<String> args;
@@ -399,7 +473,7 @@ Error EditorExportPlatformOSX::_code_sign(const Ref<EditorExportPreset> &p_prese
Error err = OS::get_singleton()->execute("codesign", args, true, nullptr, &str, nullptr, true);
ERR_FAIL_COND_V(err != OK, err);
- print_line("codesign (" + p_path + "): " + str);
+ print_line("codesign (" + p_path + "):\n" + str);
if (str.find("no identity found") != -1) {
EditorNode::add_io_error("codesign: no identity found");
return FAILED;
@@ -714,6 +788,14 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
}
}
+ bool noto_enabled = p_preset->get("notarization/enable");
+ if (err == OK && noto_enabled) {
+ if (ep.step("Sending archive for notarization", 4)) {
+ return ERR_SKIP;
+ }
+ err = _notarize(p_preset, p_path);
+ }
+
// Clean up temporary .app dir.
OS::get_singleton()->move_to_trash(tmp_app_path_name);
}
@@ -803,6 +885,41 @@ bool EditorExportPlatformOSX::can_export(const Ref<EditorExportPreset> &p_preset
valid = dvalid || rvalid;
r_missing_templates = !valid;
+ String identifier = p_preset->get("application/identifier");
+ String pn_err;
+ if (!is_package_name_valid(identifier, &pn_err)) {
+ err += TTR("Invalid bundle identifier:") + " " + pn_err + "\n";
+ valid = false;
+ }
+
+ bool sign_enabled = p_preset->get("codesign/enable");
+ if (sign_enabled) {
+ if (p_preset->get("codesign/identity") == "") {
+ err += TTR("Codesign: identity not specified.") + "\n";
+ valid = false;
+ }
+ }
+ bool noto_enabled = p_preset->get("notarization/enable");
+ if (noto_enabled) {
+ if (!sign_enabled) {
+ err += TTR("Notarization: code signing required.") + "\n";
+ valid = false;
+ }
+ bool hr_enabled = p_preset->get("codesign/hardened_runtime");
+ if (!hr_enabled) {
+ err += TTR("Notarization: hardened runtime required.") + "\n";
+ valid = false;
+ }
+ if (p_preset->get("notarization/apple_id_name") == "") {
+ err += TTR("Notarization: Apple ID name not specified.") + "\n";
+ valid = false;
+ }
+ if (p_preset->get("notarization/apple_id_password") == "") {
+ err += TTR("Notarization: Apple ID password not specified.") + "\n";
+ valid = false;
+ }
+ }
+
if (!err.empty()) {
r_error = err;
}
diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp
index db42908f89..cb4716bd65 100644
--- a/platform/uwp/export/export.cpp
+++ b/platform/uwp/export/export.cpp
@@ -743,23 +743,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
// TODO: Add resource creation or image rescaling to enable other scales:
// 1.25, 1.5, 2.0
- real_t scales[] = { 1.0 };
- bool valid_w = false;
- bool valid_h = false;
-
- for (int i = 0; i < 1; i++) {
- int w = ceil(p_width * scales[i]);
- int h = ceil(p_height * scales[i]);
-
- if (w == p_image->get_width()) {
- valid_w = true;
- }
- if (h == p_image->get_height()) {
- valid_h = true;
- }
- }
-
- return valid_w && valid_h;
+ return p_width == p_image->get_width() && p_height == p_image->get_height();
}
Vector<uint8_t> _fix_manifest(const Ref<EditorExportPreset> &p_preset, const Vector<uint8_t> &p_template, bool p_give_internet) const {
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 9d8344fa7e..0b7130db74 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -1372,70 +1372,99 @@ void DisplayServerWindows::enable_for_stealing_focus(OS::ProcessID pid) {
AllowSetForegroundWindow(pid);
}
-DisplayServer::LatinKeyboardVariant DisplayServerWindows::get_latin_keyboard_variant() const {
- _THREAD_SAFE_METHOD_
+int DisplayServerWindows::keyboard_get_layout_count() const {
+ return GetKeyboardLayoutList(0, NULL);
+}
- unsigned long azerty[] = {
- 0x00020401, // Arabic (102) AZERTY
- 0x0001080c, // Belgian (Comma)
- 0x0000080c, // Belgian French
- 0x0000040c, // French
- 0 // <--- STOP MARK
- };
- unsigned long qwertz[] = {
- 0x0000041a, // Croation
- 0x00000405, // Czech
- 0x00000407, // German
- 0x00010407, // German (IBM)
- 0x0000040e, // Hungarian
- 0x0000046e, // Luxembourgish
- 0x00010415, // Polish (214)
- 0x00000418, // Romanian (Legacy)
- 0x0000081a, // Serbian (Latin)
- 0x0000041b, // Slovak
- 0x00000424, // Slovenian
- 0x0001042e, // Sorbian Extended
- 0x0002042e, // Sorbian Standard
- 0x0000042e, // Sorbian Standard (Legacy)
- 0x0000100c, // Swiss French
- 0x00000807, // Swiss German
- 0 // <--- STOP MARK
- };
- unsigned long dvorak[] = {
- 0x00010409, // US-Dvorak
- 0x00030409, // US-Dvorak for left hand
- 0x00040409, // US-Dvorak for right hand
- 0 // <--- STOP MARK
- };
+int DisplayServerWindows::keyboard_get_current_layout() const {
+ HKL cur_layout = GetKeyboardLayout(0);
+
+ int layout_count = GetKeyboardLayoutList(0, NULL);
+ HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
+ GetKeyboardLayoutList(layout_count, layouts);
+
+ for (int i = 0; i < layout_count; i++) {
+ if (cur_layout == layouts[i]) {
+ memfree(layouts);
+ return i;
+ }
+ }
+ memfree(layouts);
+ return -1;
+}
+
+void DisplayServerWindows::keyboard_set_current_layout(int p_index) {
+ int layout_count = GetKeyboardLayoutList(0, NULL);
+
+ ERR_FAIL_INDEX(p_index, layout_count);
+
+ HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
+ GetKeyboardLayoutList(layout_count, layouts);
+ ActivateKeyboardLayout(layouts[p_index], KLF_SETFORPROCESS);
+ memfree(layouts);
+}
+
+String DisplayServerWindows::keyboard_get_layout_language(int p_index) const {
+ int layout_count = GetKeyboardLayoutList(0, NULL);
- char name[KL_NAMELENGTH + 1];
- name[0] = 0;
- GetKeyboardLayoutNameA(name);
+ ERR_FAIL_INDEX_V(p_index, layout_count, "");
- unsigned long hex = strtoul(name, nullptr, 16);
+ HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
+ GetKeyboardLayoutList(layout_count, layouts);
- int i = 0;
- while (azerty[i] != 0) {
- if (azerty[i] == hex)
- return LATIN_KEYBOARD_AZERTY;
- i++;
+ wchar_t buf[LOCALE_NAME_MAX_LENGTH];
+ memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(wchar_t));
+ LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0);
+
+ memfree(layouts);
+
+ return String(buf).substr(0, 2);
+}
+
+String _get_full_layout_name_from_registry(HKL p_layout) {
+ String id = "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\" + String::num_int64((int64_t)p_layout, 16, false).lpad(8, "0");
+ String ret;
+
+ HKEY hkey;
+ wchar_t layout_text[1024];
+ memset(layout_text, 0, 1024 * sizeof(wchar_t));
+
+ if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, (LPCWSTR)id.c_str(), 0, KEY_QUERY_VALUE, &hkey) != ERROR_SUCCESS) {
+ return ret;
}
- i = 0;
- while (qwertz[i] != 0) {
- if (qwertz[i] == hex)
- return LATIN_KEYBOARD_QWERTZ;
- i++;
+ DWORD buffer = 1024;
+ DWORD vtype = REG_SZ;
+ if (RegQueryValueExW(hkey, L"Layout Text", NULL, &vtype, (LPBYTE)layout_text, &buffer) == ERROR_SUCCESS) {
+ ret = String(layout_text);
}
+ RegCloseKey(hkey);
+ return ret;
+}
+
+String DisplayServerWindows::keyboard_get_layout_name(int p_index) const {
+ int layout_count = GetKeyboardLayoutList(0, NULL);
+
+ ERR_FAIL_INDEX_V(p_index, layout_count, "");
+
+ HKL *layouts = (HKL *)memalloc(layout_count * sizeof(HKL));
+ GetKeyboardLayoutList(layout_count, layouts);
- i = 0;
- while (dvorak[i] != 0) {
- if (dvorak[i] == hex)
- return LATIN_KEYBOARD_DVORAK;
- i++;
+ String ret = _get_full_layout_name_from_registry(layouts[p_index]); // Try reading full name from Windows registry, fallback to locale name if failed (e.g. on Wine).
+ if (ret == String()) {
+ wchar_t buf[LOCALE_NAME_MAX_LENGTH];
+ memset(buf, 0, LOCALE_NAME_MAX_LENGTH * sizeof(wchar_t));
+ LCIDToLocaleName(MAKELCID(LOWORD(layouts[p_index]), SORT_DEFAULT), buf, LOCALE_NAME_MAX_LENGTH, 0);
+
+ wchar_t name[1024];
+ memset(name, 0, 1024 * sizeof(wchar_t));
+ GetLocaleInfoEx(buf, LOCALE_SLOCALIZEDDISPLAYNAME, (LPWSTR)&name, 1024);
+
+ ret = String(name);
}
+ memfree(layouts);
- return LATIN_KEYBOARD_QWERTY;
+ return ret;
}
void DisplayServerWindows::process_events() {
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index caf8598dc2..995ced0809 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -523,7 +523,11 @@ public:
virtual void enable_for_stealing_focus(OS::ProcessID pid);
- virtual LatinKeyboardVariant get_latin_keyboard_variant() const;
+ virtual int keyboard_get_layout_count() const;
+ virtual int keyboard_get_current_layout() const;
+ virtual void keyboard_set_current_layout(int p_index);
+ virtual String keyboard_get_layout_language(int p_index) const;
+ virtual String keyboard_get_layout_name(int p_index) const;
virtual void process_events();
diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp
index 00e9af3bb7..f2f549e851 100644
--- a/scene/2d/path_2d.cpp
+++ b/scene/2d/path_2d.cpp
@@ -149,11 +149,7 @@ void Path2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_curve", "curve"), &Path2D::set_curve);
ClassDB::bind_method(D_METHOD("get_curve"), &Path2D::get_curve);
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve2D"), "set_curve", "get_curve");
-}
-
-Path2D::Path2D() {
- set_curve(Ref<Curve2D>(memnew(Curve2D))); //create one by default
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_curve", "get_curve");
}
/////////////////////////////////////////////////////////////////////////////////
diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h
index 288ef698e7..38fcca0323 100644
--- a/scene/2d/path_2d.h
+++ b/scene/2d/path_2d.h
@@ -55,7 +55,7 @@ public:
void set_curve(const Ref<Curve2D> &p_curve);
Ref<Curve2D> get_curve() const;
- Path2D();
+ Path2D() {}
};
class PathFollow2D : public Node2D {
diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp
index dcfdf8efcf..40d988ff9f 100644
--- a/scene/3d/path_3d.cpp
+++ b/scene/3d/path_3d.cpp
@@ -77,15 +77,11 @@ void Path3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_curve", "curve"), &Path3D::set_curve);
ClassDB::bind_method(D_METHOD("get_curve"), &Path3D::get_curve);
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve3D"), "set_curve", "get_curve");
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve3D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT), "set_curve", "get_curve");
ADD_SIGNAL(MethodInfo("curve_changed"));
}
-Path3D::Path3D() {
- set_curve(Ref<Curve3D>(memnew(Curve3D))); //create one by default
-}
-
//////////////
void PathFollow3D::_update_transform() {
diff --git a/scene/3d/path_3d.h b/scene/3d/path_3d.h
index 5a33016bc6..7f227a8a6f 100644
--- a/scene/3d/path_3d.h
+++ b/scene/3d/path_3d.h
@@ -49,7 +49,7 @@ public:
void set_curve(const Ref<Curve3D> &p_curve);
Ref<Curve3D> get_curve() const;
- Path3D();
+ Path3D() {}
};
class PathFollow3D : public Node3D {
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 2f5af0eda0..92508f7fd2 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -1970,6 +1970,9 @@ void RichTextLabel::clear() {
selection.click = nullptr;
selection.active = false;
current_idx = 1;
+ if (scroll_follow) {
+ scroll_following = true;
+ }
if (fixed_width != -1) {
minimum_size_changed();
@@ -2409,6 +2412,17 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) {
}
}
+ Vector<ItemFX *> fx_items;
+ for (List<Item *>::Element *E = main->subitems.front(); E; E = E->next()) {
+ Item *subitem = static_cast<Item *>(E->get());
+ _fetch_item_fx_stack(subitem, fx_items);
+
+ if (fx_items.size()) {
+ set_process_internal(true);
+ break;
+ }
+ }
+
return OK;
}
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 8c4d9a5ece..0d48fde642 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -435,7 +435,30 @@ void TabContainer::_notification(int p_what) {
void TabContainer::_on_theme_changed() {
if (get_tab_count() > 0) {
- set_current_tab(get_current_tab());
+ _repaint();
+ update();
+ }
+}
+
+void TabContainer::_repaint() {
+ Ref<StyleBox> sb = get_theme_stylebox("panel");
+ Vector<Control *> tabs = _get_tabs();
+ for (int i = 0; i < tabs.size(); i++) {
+ Control *c = tabs[i];
+ if (i == current) {
+ c->show();
+ c->set_anchors_and_margins_preset(Control::PRESET_WIDE);
+ if (tabs_visible) {
+ c->set_margin(MARGIN_TOP, _get_top_margin());
+ }
+ c->set_margin(Margin(MARGIN_TOP), c->get_margin(Margin(MARGIN_TOP)) + sb->get_margin(Margin(MARGIN_TOP)));
+ c->set_margin(Margin(MARGIN_LEFT), c->get_margin(Margin(MARGIN_LEFT)) + sb->get_margin(Margin(MARGIN_LEFT)));
+ c->set_margin(Margin(MARGIN_RIGHT), c->get_margin(Margin(MARGIN_RIGHT)) - sb->get_margin(Margin(MARGIN_RIGHT)));
+ c->set_margin(Margin(MARGIN_BOTTOM), c->get_margin(Margin(MARGIN_BOTTOM)) - sb->get_margin(Margin(MARGIN_BOTTOM)));
+
+ } else {
+ c->hide();
+ }
}
}
@@ -551,25 +574,7 @@ void TabContainer::set_current_tab(int p_current) {
int pending_previous = current;
current = p_current;
- Ref<StyleBox> sb = get_theme_stylebox("panel");
- Vector<Control *> tabs = _get_tabs();
- for (int i = 0; i < tabs.size(); i++) {
- Control *c = tabs[i];
- if (i == current) {
- c->show();
- c->set_anchors_and_margins_preset(Control::PRESET_WIDE);
- if (tabs_visible) {
- c->set_margin(MARGIN_TOP, _get_top_margin());
- }
- c->set_margin(Margin(MARGIN_TOP), c->get_margin(Margin(MARGIN_TOP)) + sb->get_margin(Margin(MARGIN_TOP)));
- c->set_margin(Margin(MARGIN_LEFT), c->get_margin(Margin(MARGIN_LEFT)) + sb->get_margin(Margin(MARGIN_LEFT)));
- c->set_margin(Margin(MARGIN_RIGHT), c->get_margin(Margin(MARGIN_RIGHT)) - sb->get_margin(Margin(MARGIN_RIGHT)));
- c->set_margin(Margin(MARGIN_BOTTOM), c->get_margin(Margin(MARGIN_BOTTOM)) - sb->get_margin(Margin(MARGIN_BOTTOM)));
-
- } else {
- c->hide();
- }
- }
+ _repaint();
_change_notify("current_tab");
diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h
index e8cde74c83..55a5d35b30 100644
--- a/scene/gui/tab_container.h
+++ b/scene/gui/tab_container.h
@@ -65,6 +65,7 @@ private:
Vector<Control *> _get_tabs() const;
int _get_tab_width(int p_index) const;
void _on_theme_changed();
+ void _repaint();
void _on_mouse_exited();
void _update_current_tab();
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index cc818cbe04..0ea11a2670 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -276,8 +276,23 @@ Error DisplayServer::dialog_input_text(String p_title, String p_description, Str
return OK;
}
-DisplayServer::LatinKeyboardVariant DisplayServer::get_latin_keyboard_variant() const {
- return LATIN_KEYBOARD_QWERTY;
+int DisplayServer::keyboard_get_layout_count() const {
+ return 0;
+}
+
+int DisplayServer::keyboard_get_current_layout() const {
+ return -1;
+}
+
+void DisplayServer::keyboard_set_current_layout(int p_index) {
+}
+
+String DisplayServer::keyboard_get_layout_language(int p_index) const {
+ return "";
+}
+
+String DisplayServer::keyboard_get_layout_name(int p_index) const {
+ return "Not supported";
}
void DisplayServer::force_process_and_drop_events() {
@@ -461,7 +476,11 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("dialog_show", "title", "description", "buttons", "callback"), &DisplayServer::dialog_show);
ClassDB::bind_method(D_METHOD("dialog_input_text", "title", "description", "existing_text", "callback"), &DisplayServer::dialog_input_text);
- ClassDB::bind_method(D_METHOD("get_latin_keyboard_variant"), &DisplayServer::get_latin_keyboard_variant);
+ ClassDB::bind_method(D_METHOD("keyboard_get_layout_count"), &DisplayServer::keyboard_get_layout_count);
+ ClassDB::bind_method(D_METHOD("keyboard_get_current_layout"), &DisplayServer::keyboard_get_current_layout);
+ ClassDB::bind_method(D_METHOD("keyboard_set_current_layout", "index"), &DisplayServer::keyboard_set_current_layout);
+ ClassDB::bind_method(D_METHOD("keyboard_get_layout_language", "index"), &DisplayServer::keyboard_get_layout_language);
+ ClassDB::bind_method(D_METHOD("keyboard_get_layout_name", "index"), &DisplayServer::keyboard_get_layout_name);
ClassDB::bind_method(D_METHOD("process_events"), &DisplayServer::process_events);
ClassDB::bind_method(D_METHOD("force_process_and_drop_events"), &DisplayServer::force_process_and_drop_events);
@@ -543,14 +562,6 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(WINDOW_FLAG_NO_FOCUS);
BIND_ENUM_CONSTANT(WINDOW_FLAG_MAX);
- BIND_ENUM_CONSTANT(LATIN_KEYBOARD_QWERTY);
- BIND_ENUM_CONSTANT(LATIN_KEYBOARD_QWERTZ);
- BIND_ENUM_CONSTANT(LATIN_KEYBOARD_AZERTY);
- BIND_ENUM_CONSTANT(LATIN_KEYBOARD_QZERTY);
- BIND_ENUM_CONSTANT(LATIN_KEYBOARD_DVORAK);
- BIND_ENUM_CONSTANT(LATIN_KEYBOARD_NEO);
- BIND_ENUM_CONSTANT(LATIN_KEYBOARD_COLEMAK);
-
BIND_ENUM_CONSTANT(WINDOW_EVENT_MOUSE_ENTER);
BIND_ENUM_CONSTANT(WINDOW_EVENT_MOUSE_EXIT);
BIND_ENUM_CONSTANT(WINDOW_EVENT_FOCUS_IN);
diff --git a/servers/display_server.h b/servers/display_server.h
index b25b1931c8..166274f8ed 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -324,17 +324,11 @@ public:
virtual Error dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback);
virtual Error dialog_input_text(String p_title, String p_description, String p_partial, const Callable &p_callback);
- enum LatinKeyboardVariant {
- LATIN_KEYBOARD_QWERTY,
- LATIN_KEYBOARD_QWERTZ,
- LATIN_KEYBOARD_AZERTY,
- LATIN_KEYBOARD_QZERTY,
- LATIN_KEYBOARD_DVORAK,
- LATIN_KEYBOARD_NEO,
- LATIN_KEYBOARD_COLEMAK,
- };
-
- virtual LatinKeyboardVariant get_latin_keyboard_variant() const;
+ virtual int keyboard_get_layout_count() const;
+ virtual int keyboard_get_current_layout() const;
+ virtual void keyboard_set_current_layout(int p_index);
+ virtual String keyboard_get_layout_language(int p_index) const;
+ virtual String keyboard_get_layout_name(int p_index) const;
virtual void process_events() = 0;
@@ -384,6 +378,5 @@ VARIANT_ENUM_CAST(DisplayServer::ScreenOrientation)
VARIANT_ENUM_CAST(DisplayServer::WindowMode)
VARIANT_ENUM_CAST(DisplayServer::WindowFlags)
VARIANT_ENUM_CAST(DisplayServer::CursorShape)
-VARIANT_ENUM_CAST(DisplayServer::LatinKeyboardVariant)
#endif // DISPLAY_SERVER_H
diff --git a/servers/physics_2d/broad_phase_2d_hash_grid.cpp b/servers/physics_2d/broad_phase_2d_hash_grid.cpp
index cfd02cef2c..ae549ed2e4 100644
--- a/servers/physics_2d/broad_phase_2d_hash_grid.cpp
+++ b/servers/physics_2d/broad_phase_2d_hash_grid.cpp
@@ -75,10 +75,7 @@ void BroadPhase2DHashGrid::_check_motion(Element *p_elem) {
if (pairing != E->get()->colliding) {
if (pairing) {
if (pair_callback) {
- void *ud = pair_callback(p_elem->owner, p_elem->subindex, E->key()->owner, E->key()->subindex, pair_userdata);
- if (ud) {
- E->get()->ud = ud;
- }
+ E->get()->ud = pair_callback(p_elem->owner, p_elem->subindex, E->key()->owner, E->key()->subindex, pair_userdata);
}
} else {
if (unpair_callback) {