summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--SConstruct7
-rw-r--r--core/extension/gdextension_interface.h2
-rw-r--r--core/object/script_language_extension.h2
-rw-r--r--core/variant/variant_utility.cpp6
-rw-r--r--core/variant/variant_utility.h1
-rw-r--r--doc/classes/@GlobalScope.xml14
-rw-r--r--doc/classes/AStarGrid2D.xml4
-rw-r--r--doc/classes/AcceptDialog.xml1
-rw-r--r--doc/classes/DisplayServer.xml59
-rw-r--r--doc/classes/ProjectSettings.xml10
-rw-r--r--doc/classes/SeparationRayShape2D.xml2
-rw-r--r--doc/classes/SeparationRayShape3D.xml2
-rw-r--r--doc/classes/SubViewportContainer.xml9
-rw-r--r--doc/classes/TextServer.xml7
-rw-r--r--doc/classes/TextServerExtension.xml6
-rw-r--r--doc/classes/Window.xml19
-rw-r--r--drivers/unix/SCsub2
-rw-r--r--drivers/vulkan/rendering_device_vulkan.cpp1
-rw-r--r--editor/animation_track_editor.cpp16
-rw-r--r--editor/animation_track_editor.h2
-rw-r--r--editor/connections_dialog.cpp85
-rw-r--r--editor/connections_dialog.h2
-rw-r--r--editor/create_dialog.cpp5
-rw-r--r--editor/editor_build_profile.cpp29
-rw-r--r--editor/editor_feature_profile.cpp25
-rw-r--r--editor/editor_help.cpp244
-rw-r--r--editor/editor_help.h27
-rw-r--r--editor/editor_inspector.cpp182
-rw-r--r--editor/editor_inspector.h8
-rw-r--r--editor/editor_node.cpp2
-rw-r--r--editor/editor_properties.cpp137
-rw-r--r--editor/editor_properties.h20
-rw-r--r--editor/editor_properties_array_dict.cpp3
-rw-r--r--editor/export/editor_export_platform.cpp24
-rw-r--r--editor/filesystem_dock.cpp71
-rw-r--r--editor/filesystem_dock.h10
-rw-r--r--editor/icons/CodeRegionFoldDownArrow.svg2
-rw-r--r--editor/icons/CodeRegionFoldedRightArrow.svg2
-rw-r--r--editor/import_dock.cpp9
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp245
-rw-r--r--editor/plugins/animation_player_editor_plugin.h27
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp12
-rw-r--r--editor/plugins/path_3d_editor_plugin.cpp43
-rw-r--r--editor/plugins/path_3d_editor_plugin.h9
-rw-r--r--editor/property_selector.cpp43
-rw-r--r--editor/scene_tree_dock.cpp26
-rw-r--r--main/main.cpp20
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp22
-rw-r--r--modules/gdscript/gdscript.cpp98
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp28
-rw-r--r--modules/gdscript/gdscript_editor.cpp6
-rw-r--r--modules/gdscript/gdscript_warning.cpp4
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd17
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out40
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/stringify.gd1
-rw-r--r--modules/gdscript/tests/scripts/utils.notest.gd84
-rw-r--r--modules/gdscript/tests/test_gdscript.cpp10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs42
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs28
-rw-r--r--modules/openxr/SCsub1
-rw-r--r--modules/openxr/action_map/openxr_action_map.cpp8
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml7
-rw-r--r--modules/openxr/extensions/openxr_eye_gaze_interaction.cpp98
-rw-r--r--modules/openxr/extensions/openxr_eye_gaze_interaction.h58
-rw-r--r--modules/openxr/openxr_interface.cpp23
-rw-r--r--modules/openxr/openxr_interface.h2
-rw-r--r--modules/openxr/register_types.cpp4
-rw-r--r--modules/text_server_adv/text_server_adv.cpp281
-rw-r--r--modules/text_server_adv/text_server_adv.h16
-rw-r--r--modules/text_server_fb/text_server_fb.cpp265
-rw-r--r--modules/text_server_fb/text_server_fb.h16
-rw-r--r--platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.kt13
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt15
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java9
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java2
-rw-r--r--platform/android/java_godot_wrapper.cpp13
-rw-r--r--platform/android/java_godot_wrapper.h4
-rw-r--r--platform/android/os_android.cpp5
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.cpp106
-rw-r--r--platform/linuxbsd/freedesktop_portal_desktop.h8
-rw-r--r--platform/linuxbsd/platform_gl.h4
-rw-r--r--platform/linuxbsd/x11/SCsub4
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp84
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h2
-rw-r--r--platform/linuxbsd/x11/gl_manager_x11_egl.cpp63
-rw-r--r--platform/linuxbsd/x11/gl_manager_x11_egl.h61
-rw-r--r--platform/macos/display_server_macos.h19
-rw-r--r--platform/macos/display_server_macos.mm604
-rw-r--r--platform/macos/export/export_plugin.cpp6
-rw-r--r--platform/macos/godot_menu_delegate.mm28
-rw-r--r--platform/macos/godot_menu_item.h1
-rw-r--r--platform/macos/godot_window_delegate.mm2
-rw-r--r--platform/windows/display_server_windows.cpp115
-rw-r--r--platform/windows/display_server_windows.h3
-rw-r--r--platform/windows/platform_windows_builders.py16
-rw-r--r--platform/windows/wgl_detect_version.cpp24
-rw-r--r--platform/windows/wgl_detect_version.h4
-rw-r--r--scene/2d/node_2d.cpp5
-rw-r--r--scene/2d/physics_body_2d.cpp7
-rw-r--r--scene/3d/node_3d.cpp11
-rw-r--r--scene/3d/physics_body_3d.cpp14
-rw-r--r--scene/gui/code_edit.cpp45
-rw-r--r--scene/gui/code_edit.h1
-rw-r--r--scene/gui/dialogs.cpp1
-rw-r--r--scene/gui/file_dialog.cpp3
-rw-r--r--scene/gui/file_dialog.h2
-rw-r--r--scene/gui/menu_bar.cpp228
-rw-r--r--scene/gui/menu_bar.h24
-rw-r--r--scene/gui/popup_menu.cpp461
-rw-r--r--scene/gui/popup_menu.h9
-rw-r--r--scene/gui/subviewport_container.cpp16
-rw-r--r--scene/gui/subviewport_container.h2
-rw-r--r--scene/main/canvas_item.cpp11
-rw-r--r--scene/main/window.cpp70
-rw-r--r--scene/main/window.h5
-rw-r--r--scene/resources/font.cpp85
-rw-r--r--scene/resources/font.h2
-rw-r--r--scene/theme/default_theme.cpp4
-rw-r--r--scene/theme/icons/folder_down_arrow.svg1
-rw-r--r--scene/theme/icons/folder_right_arrow.svg1
-rw-r--r--scene/theme/icons/region_folded.svg1
-rw-r--r--scene/theme/icons/region_unfolded.svg1
-rw-r--r--servers/display_server.cpp22
-rw-r--r--servers/display_server.h9
-rw-r--r--servers/rendering/renderer_rd/renderer_compositor_rd.cpp5
-rw-r--r--servers/rendering/renderer_viewport.cpp17
-rw-r--r--servers/rendering/renderer_viewport.h2
-rw-r--r--servers/rendering/rendering_server_default.cpp4
-rw-r--r--servers/rendering/shader_compiler.cpp26
-rw-r--r--servers/rendering/shader_language.cpp103
-rw-r--r--servers/rendering/shader_language.h3
-rw-r--r--servers/text/text_server_extension.cpp7
-rw-r--r--servers/text/text_server_extension.h3
-rw-r--r--servers/text_server.cpp1
-rw-r--r--servers/text_server.h1
-rw-r--r--tests/scene/test_code_edit.h61
137 files changed, 3675 insertions, 1461 deletions
diff --git a/SConstruct b/SConstruct
index 4f411d6d0a..294ae82b7d 100644
--- a/SConstruct
+++ b/SConstruct
@@ -988,9 +988,10 @@ if selected_platform in platform_list:
# Check for the existence of headers
conf = Configure(env)
if "check_c_headers" in env:
- for header in env["check_c_headers"]:
- if conf.CheckCHeader(header[0]):
- env.AppendUnique(CPPDEFINES=[header[1]])
+ headers = env["check_c_headers"]
+ for header in headers:
+ if conf.CheckCHeader(header):
+ env.AppendUnique(CPPDEFINES=[headers[header]])
elif selected_platform != "":
if selected_platform == "list":
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index 35602b76dd..fbd1480e69 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -510,7 +510,7 @@ typedef struct {
GDExtensionScriptInstanceGet get_func;
GDExtensionScriptInstanceGetPropertyList get_property_list_func;
GDExtensionScriptInstanceFreePropertyList free_property_list_func;
- GDExtensionScriptInstanceGetClassCategory get_class_category_func;
+ GDExtensionScriptInstanceGetClassCategory get_class_category_func; // Optional. Set to NULL for the default behavior.
GDExtensionScriptInstancePropertyCanRevert property_can_revert_func;
GDExtensionScriptInstancePropertyGetRevert property_get_revert_func;
diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h
index b682efec12..beb8064a33 100644
--- a/core/object/script_language_extension.h
+++ b/core/object/script_language_extension.h
@@ -676,13 +676,11 @@ public:
if (native_info->get_class_category_func(instance, &gdext_class_category)) {
p_list->push_back(PropertyInfo(gdext_class_category));
}
-#ifndef DISABLE_DEPRECATED
} else {
Ref<Script> script = get_script();
if (script.is_valid()) {
p_list->push_back(script->get_class_category());
}
-#endif // DISABLE_DEPRECATED
}
}
#endif // TOOLS_ENABLED
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index d80bc473e5..6f334d1859 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -875,6 +875,11 @@ String VariantUtilityFunctions::error_string(Error error) {
return String(error_names[error]);
}
+String VariantUtilityFunctions::type_string(Variant::Type p_type) {
+ ERR_FAIL_INDEX_V_MSG((int)p_type, (int)Variant::VARIANT_MAX, "<invalid type>", "Invalid type argument to type_string(), use the TYPE_* constants.");
+ return Variant::get_type_name(p_type);
+}
+
void VariantUtilityFunctions::print(const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
String s;
for (int i = 0; i < p_arg_count; i++) {
@@ -1713,6 +1718,7 @@ void Variant::_register_variant_utility_functions() {
FUNCBINDR(type_convert, sarray("variant", "type"), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDVARARGS(str, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDR(error_string, sarray("error"), Variant::UTILITY_FUNC_TYPE_GENERAL);
+ FUNCBINDR(type_string, sarray("type"), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDVARARGV(print, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDVARARGV(print_rich, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
FUNCBINDVARARGV(printerr, sarray(), Variant::UTILITY_FUNC_TYPE_GENERAL);
diff --git a/core/variant/variant_utility.h b/core/variant/variant_utility.h
index 8130f30881..a56c84a8e9 100644
--- a/core/variant/variant_utility.h
+++ b/core/variant/variant_utility.h
@@ -130,6 +130,7 @@ struct VariantUtilityFunctions {
static Variant type_convert(const Variant &p_variant, const Variant::Type p_type);
static String str(const Variant **p_args, int p_arg_count, Callable::CallError &r_error);
static String error_string(Error error);
+ static String type_string(Variant::Type p_type);
static void print(const Variant **p_args, int p_arg_count, Callable::CallError &r_error);
static void print_rich(const Variant **p_args, int p_arg_count, Callable::CallError &r_error);
#undef print_verbose
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 32b5d9eb24..c854d9b54e 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -1403,6 +1403,19 @@
[/codeblock]
</description>
</method>
+ <method name="type_string">
+ <return type="String" />
+ <param index="0" name="type" type="int" />
+ <description>
+ Returns a human-readable name of the given [param type], using the [enum Variant.Type] values.
+ [codeblock]
+ print(TYPE_INT) # Prints 2.
+ print(type_string(TYPE_INT)) # Prints "int".
+ print(type_string(TYPE_STRING)) # Prints "String".
+ [/codeblock]
+ See also [method typeof].
+ </description>
+ </method>
<method name="typeof">
<return type="int" />
<param index="0" name="variable" type="Variant" />
@@ -1417,6 +1430,7 @@
else:
print("Unexpected result")
[/codeblock]
+ See also [method type_string].
</description>
</method>
<method name="var_to_bytes">
diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml
index 9924af08c4..277630588a 100644
--- a/doc/classes/AStarGrid2D.xml
+++ b/doc/classes/AStarGrid2D.xml
@@ -113,14 +113,14 @@
<param index="0" name="x" type="int" />
<param index="1" name="y" type="int" />
<description>
- Returns [code]true[/code] if the [param x] and [param y] is a valid grid coordinate (id).
+ Returns [code]true[/code] if the [param x] and [param y] is a valid grid coordinate (id), i.e. if it is inside [member region]. Equivalent to [code]region.has_point(Vector2i(x, y))[/code].
</description>
</method>
<method name="is_in_boundsv" qualifiers="const">
<return type="bool" />
<param index="0" name="id" type="Vector2i" />
<description>
- Returns [code]true[/code] if the [param id] vector is a valid grid coordinate.
+ Returns [code]true[/code] if the [param id] vector is a valid grid coordinate, i.e. if it is inside [member region]. Equivalent to [code]region.has_point(id)[/code].
</description>
</method>
<method name="is_point_solid" qualifiers="const">
diff --git a/doc/classes/AcceptDialog.xml b/doc/classes/AcceptDialog.xml
index 202a84fb58..59cbb38036 100644
--- a/doc/classes/AcceptDialog.xml
+++ b/doc/classes/AcceptDialog.xml
@@ -72,6 +72,7 @@
The text displayed by the dialog.
</member>
<member name="exclusive" type="bool" setter="set_exclusive" getter="is_exclusive" overrides="Window" default="true" />
+ <member name="keep_title_visible" type="bool" setter="set_keep_title_visible" getter="get_keep_title_visible" overrides="Window" default="true" />
<member name="ok_button_text" type="String" setter="set_ok_button_text" getter="get_ok_button_text" default="&quot;OK&quot;">
The text displayed by the OK button (see [method get_ok_button]).
</member>
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 508b0b7482..3c7d66a67b 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -119,7 +119,7 @@
<param index="6" name="callback" type="Callable" />
<description>
Displays OS native dialog for selecting files or directories in the file system.
- Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths[/code].
+ Callbacks have the following arguments: [code]bool status, PackedStringArray selected_paths, int selected_filter_index[/code].
[b]Note:[/b] This method is implemented if the display server has the [constant FEATURE_NATIVE_DIALOG] feature.
[b]Note:[/b] This method is implemented on Linux, Windows and macOS.
[b]Note:[/b] [param current_directory] might be ignored.
@@ -577,6 +577,16 @@
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
+ <method name="global_menu_is_item_hidden" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="idx" type="int" />
+ <description>
+ Returns [code]true[/code] if the item at index [param idx] is hidden.
+ See [method global_menu_set_item_hidden] for more info on how to hide an item.
+ [b]Note:[/b] This method is implemented only on macOS.
+ </description>
+ </method>
<method name="global_menu_is_item_radio_checkable" qualifiers="const">
<return type="bool" />
<param index="0" name="menu_root" type="String" />
@@ -648,6 +658,27 @@
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
+ <method name="global_menu_set_item_hidden">
+ <return type="void" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="idx" type="int" />
+ <param index="2" name="hidden" type="bool" />
+ <description>
+ Hides/shows the item at index [param idx]. When it is hidden, an item does not appear in a menu and its action cannot be invoked.
+ [b]Note:[/b] This method is implemented only on macOS.
+ </description>
+ </method>
+ <method name="global_menu_set_item_hover_callbacks">
+ <return type="void" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="idx" type="int" />
+ <param index="2" name="callback" type="Callable" />
+ <description>
+ Sets the callback of the item at index [param idx]. The callback is emitted when an item is hovered.
+ [b]Note:[/b] The [param callback] Callable needs to accept exactly one Variant parameter, the parameter passed to the Callable will be the value passed to the [code]tag[/code] parameter when the menu item was created.
+ [b]Note:[/b] This method is implemented only on macOS.
+ </description>
+ </method>
<method name="global_menu_set_item_icon">
<return type="void" />
<param index="0" name="menu_root" type="String" />
@@ -751,6 +782,15 @@
[b]Note:[/b] This method is implemented only on macOS.
</description>
</method>
+ <method name="global_menu_set_popup_callbacks">
+ <return type="void" />
+ <param index="0" name="menu_root" type="String" />
+ <param index="1" name="open_callback" type="Callable" />
+ <param index="2" name="close_callback" type="Callable" />
+ <description>
+ Registers callables to emit when the menu is respectively about to show or closed.
+ </description>
+ </method>
<method name="has_feature" qualifiers="const">
<return type="bool" />
<param index="0" name="feature" type="int" enum="DisplayServer.Feature" />
@@ -1291,6 +1331,15 @@
Returns the size of the window specified by [param window_id] (in pixels), including the borders drawn by the operating system. See also [method window_get_size].
</description>
</method>
+ <method name="window_get_title_size" qualifiers="const">
+ <return type="Vector2i" />
+ <param index="0" name="title" type="String" />
+ <param index="1" name="window_id" type="int" default="0" />
+ <description>
+ Returns the estimated window title bar size (including text and window buttons) for the window specified by [param window_id] (in pixels). This method does not change the window title.
+ [b]Note:[/b] This method is implemented on macOS and Windows.
+ </description>
+ </method>
<method name="window_get_vsync_mode" qualifiers="const">
<return type="int" enum="DisplayServer.VSyncMode" />
<param index="0" name="window_id" type="int" default="0" />
@@ -1787,14 +1836,16 @@
</constant>
<constant name="WINDOW_MODE_FULLSCREEN" value="3" enum="WindowMode">
Full screen mode with full multi-window support.
- Full screen window cover the entire display area of a screen, have no border or decorations. Display video mode is not changed.
+ Full screen window covers the entire display area of a screen and has no decorations. The display's video mode is not changed.
+ [b]On Windows:[/b] Multi-window full-screen mode has a 1px border of the [member ProjectSettings.rendering/environment/defaults/default_clear_color] color.
+ [b]On macOS:[/b] A new desktop is used to display the running project.
[b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
</constant>
<constant name="WINDOW_MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="WindowMode">
A single window full screen mode. This mode has less overhead, but only one window can be open on a given screen at a time (opening a child window or application switching will trigger a full screen transition).
- Full screen window cover the entire display area of a screen, have no border or decorations. Display video mode is not changed.
+ Full screen window covers the entire display area of a screen and has no border or decorations. The display's video mode is not changed.
[b]On Windows:[/b] Depending on video driver, full screen transition might cause screens to go black for a moment.
- [b]On macOS:[/b] Exclusive full screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen.
+ [b]On macOS:[/b] A new desktop is used to display the running project. Exclusive full screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen.
[b]On Linux (X11):[/b] Exclusive full screen mode bypasses compositor.
[b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
</constant>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index ba9c745663..39effa4b68 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -2366,6 +2366,13 @@
<member name="rendering/gl_compatibility/driver.windows" type="String" setter="" getter="" default="&quot;opengl3&quot;">
Windows override for [member rendering/gl_compatibility/driver].
</member>
+ <member name="rendering/gl_compatibility/fallback_to_angle" type="bool" setter="" getter="" default="true">
+ If [code]true[/code], the compatibility renderer will fall back to ANGLE if native OpenGL is not supported or the device is listed in [member rendering/gl_compatibility/force_angle_on_devices].
+ </member>
+ <member name="rendering/gl_compatibility/force_angle_on_devices" type="Array" setter="" getter="" default="[]">
+ An [Array] of devices which should always use the ANGLE renderer.
+ Each entry is a [Dictionary] with the following keys: [code]vendor[/code] and [code]name[/code]. [code]name[/code] can be set to [code]*[/code] to add all devices with the specified [code]vendor[/code].
+ </member>
<member name="rendering/gl_compatibility/item_buffer_size" type="int" setter="" getter="" default="16384">
Maximum number of canvas items commands that can be drawn in a single viewport update. If more render commands are issued they will be ignored. Decreasing this limit may improve performance on bandwidth limited devices. Increase this limit if you find that not all objects are being drawn in a frame.
</member>
@@ -2718,6 +2725,9 @@
<member name="xr/openxr/environment_blend_mode" type="int" setter="" getter="" default="&quot;0&quot;">
Specify how OpenXR should blend in the environment. This is specific to certain AR and passthrough devices where camera images are blended in by the XR compositor.
</member>
+ <member name="xr/openxr/extensions/eye_gaze_interaction" type="bool" setter="" getter="" default="false">
+ Specify whether to enable eye tracking for this project. Depending on the platform, additional export configuration may be needed.
+ </member>
<member name="xr/openxr/form_factor" type="int" setter="" getter="" default="&quot;0&quot;">
Specify whether OpenXR should be configured for an HMD or a hand held device.
</member>
diff --git a/doc/classes/SeparationRayShape2D.xml b/doc/classes/SeparationRayShape2D.xml
index 6237584056..9c805d4ede 100644
--- a/doc/classes/SeparationRayShape2D.xml
+++ b/doc/classes/SeparationRayShape2D.xml
@@ -4,7 +4,7 @@
A 2D ray shape used for physics collision that tries to separate itself from any collider.
</brief_description>
<description>
- A 2D ray shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape2D]. When a [SeparationRayShape2D] collides with an object, it tries to separate itself from it by moving its endpoint to the collision point. It can for example be used for spears falling from the sky.
+ A 2D ray shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape2D]. When a [SeparationRayShape2D] collides with an object, it tries to separate itself from it by moving its endpoint to the collision point. For example, a [SeparationRayShape2D] next to a character can allow it to instantly move up when touching stairs.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/SeparationRayShape3D.xml b/doc/classes/SeparationRayShape3D.xml
index aaf8791224..afb030ad0e 100644
--- a/doc/classes/SeparationRayShape3D.xml
+++ b/doc/classes/SeparationRayShape3D.xml
@@ -4,7 +4,7 @@
A 3D ray shape used for physics collision that tries to separate itself from any collider.
</brief_description>
<description>
- A 3D ray shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape3D]. When a [SeparationRayShape3D] collides with an object, it tries to separate itself from it by moving its endpoint to the collision point. It can for example be used for spears falling from the sky.
+ A 3D ray shape, intended for use in physics. Usually used to provide a shape for a [CollisionShape3D]. When a [SeparationRayShape3D] collides with an object, it tries to separate itself from it by moving its endpoint to the collision point. For example, a [SeparationRayShape3D] next to a character can allow it to instantly move up when touching stairs.
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/SubViewportContainer.xml b/doc/classes/SubViewportContainer.xml
index 08e7ca23f7..e7d7883c78 100644
--- a/doc/classes/SubViewportContainer.xml
+++ b/doc/classes/SubViewportContainer.xml
@@ -10,6 +10,15 @@
</description>
<tutorials>
</tutorials>
+ <methods>
+ <method name="_propagate_input_event" qualifiers="virtual const">
+ <return type="bool" />
+ <param index="0" name="event" type="InputEvent" />
+ <description>
+ Virtual method to be implemented by the user. If it returns [code]true[/code], the [param event] is propagated to [SubViewport] children. Propagation doesn't happen if it returns [code]false[/code]. If the function is not implemented, all events are propagated to SubViewports.
+ </description>
+ </method>
+ </methods>
<members>
<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="1" />
<member name="stretch" type="bool" setter="set_stretch" getter="is_stretch_enabled" default="false">
diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml
index 136d560deb..cfd6d3b281 100644
--- a/doc/classes/TextServer.xml
+++ b/doc/classes/TextServer.xml
@@ -15,6 +15,13 @@
Creates a new, empty font cache entry resource. To free the resulting resource, use the [method free_rid] method.
</description>
</method>
+ <method name="create_font_linked_variation">
+ <return type="RID" />
+ <param index="0" name="font_rid" type="RID" />
+ <description>
+ Creates a new variation existing font which is reusing the same glyph cache and font data. To free the resulting resource, use the [method free_rid] method.
+ </description>
+ </method>
<method name="create_shaped_text">
<return type="RID" />
<param index="0" name="direction" type="int" enum="TextServer.Direction" default="0" />
diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml
index 82162e77be..f8e21ece35 100644
--- a/doc/classes/TextServerExtension.xml
+++ b/doc/classes/TextServerExtension.xml
@@ -19,6 +19,12 @@
<description>
</description>
</method>
+ <method name="_create_font_linked_variation" qualifiers="virtual">
+ <return type="RID" />
+ <param index="0" name="font_rid" type="RID" />
+ <description>
+ </description>
+ </method>
<method name="_create_shaped_text" qualifiers="virtual">
<return type="RID" />
<param index="0" name="direction" type="int" enum="TextServer.Direction" />
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index b03ef3ab4e..5f6b1960b7 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -593,6 +593,9 @@
<member name="initial_position" type="int" setter="set_initial_position" getter="get_initial_position" enum="Window.WindowInitialPosition" default="0">
Specifies the initial type of position for the [Window]. See [enum WindowInitialPosition] constants.
</member>
+ <member name="keep_title_visible" type="bool" setter="set_keep_title_visible" getter="get_keep_title_visible" default="false">
+ If [code]true[/code], the [Window] width is expanded to keep the title bar text fully visible.
+ </member>
<member name="max_size" type="Vector2i" setter="set_max_size" getter="get_max_size" default="Vector2i(0, 0)">
If non-zero, the [Window] can't be resized to be bigger than this size.
[b]Note:[/b] This property will be ignored if the value is lower than [member min_size].
@@ -785,13 +788,19 @@
Maximized window mode, i.e. [Window] will occupy whole screen area except task bar and still display its borders. Normally happens when the maximize button is pressed.
</constant>
<constant name="MODE_FULLSCREEN" value="3" enum="Mode">
- Full screen window mode. Note that this is not [i]exclusive[/i] full screen. On Windows and Linux, a borderless window is used to emulate full screen. On macOS, a new desktop is used to display the running project.
- Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
+ Full screen mode with full multi-window support.
+ Full screen window covers the entire display area of a screen and has no decorations. The display's video mode is not changed.
+ [b]On Windows:[/b] Multi-window full-screen mode has a 1px border of the [member ProjectSettings.rendering/environment/defaults/default_clear_color] color.
+ [b]On macOS:[/b] A new desktop is used to display the running project.
+ [b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
</constant>
<constant name="MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="Mode">
- Exclusive full screen window mode. This mode is implemented on Windows only. On other platforms, it is equivalent to [constant MODE_FULLSCREEN].
- Only one window in exclusive full screen mode can be visible on a given screen at a time. If multiple windows are in exclusive full screen mode for the same screen, the last one being set to this mode takes precedence.
- Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
+ A single window full screen mode. This mode has less overhead, but only one window can be open on a given screen at a time (opening a child window or application switching will trigger a full screen transition).
+ Full screen window covers the entire display area of a screen and has no border or decorations. The display's video mode is not changed.
+ [b]On Windows:[/b] Depending on video driver, full screen transition might cause screens to go black for a moment.
+ [b]On macOS:[/b] A new desktop is used to display the running project. Exclusive full screen mode prevents Dock and Menu from showing up when the mouse pointer is hovering the edge of the screen.
+ [b]On Linux (X11):[/b] Exclusive full screen mode bypasses compositor.
+ [b]Note:[/b] Regardless of the platform, enabling full screen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling full screen mode.
</constant>
<constant name="FLAG_RESIZE_DISABLED" value="0" enum="Flags">
The window can't be resized by dragging its resize grip. It's still possible to resize the window using [member size]. This flag is ignored for full screen windows. Set with [member unresizable].
diff --git a/drivers/unix/SCsub b/drivers/unix/SCsub
index 91ef613546..146563a3b6 100644
--- a/drivers/unix/SCsub
+++ b/drivers/unix/SCsub
@@ -4,4 +4,4 @@ Import("env")
env.add_source_files(env.drivers_sources, "*.cpp")
-env["check_c_headers"] = [["mntent.h", "HAVE_MNTENT"]]
+env["check_c_headers"] = {"mntent.h": "HAVE_MNTENT"}
diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp
index 11321b2121..e553b2b522 100644
--- a/drivers/vulkan/rendering_device_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_vulkan.cpp
@@ -5054,6 +5054,7 @@ RID RenderingDeviceVulkan::shader_create_from_bytecode(const Vector<uint8_t> &p_
}
Shader *shader = shader_owner.get_or_null(id);
+ ERR_FAIL_NULL_V(shader, RID());
shader->vertex_input_mask = vertex_input_mask;
shader->fragment_output_mask = fragment_output_mask;
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 7105ff280a..e2852d5a31 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -3249,6 +3249,22 @@ void AnimationTrackEditGroup::_notification(int p_what) {
}
}
+void AnimationTrackEditGroup::gui_input(const Ref<InputEvent> &p_event) {
+ ERR_FAIL_COND(p_event.is_null());
+
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
+ Point2 pos = mb->get_position();
+ Rect2 node_name_rect = Rect2(0, 0, timeline->get_name_limit(), get_size().height);
+
+ if (node_name_rect.has_point(pos)) {
+ EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection();
+ editor_selection->clear();
+ editor_selection->add_node(root->get_node(node));
+ }
+ }
+}
+
void AnimationTrackEditGroup::set_type_and_name(const Ref<Texture2D> &p_type, const String &p_name, const NodePath &p_node) {
icon = p_type;
node_name = p_name;
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 5327099517..5592d43ffe 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -349,6 +349,8 @@ class AnimationTrackEditGroup : public Control {
protected:
void _notification(int p_what);
+ virtual void gui_input(const Ref<InputEvent> &p_event) override;
+
public:
void set_type_and_name(const Ref<Texture2D> &p_type, const String &p_name, const NodePath &p_node);
virtual Size2 get_minimum_size() const override;
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 208253d617..31659d4d4e 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -835,35 +835,9 @@ ConnectDialog::~ConnectDialog() {
//////////////////////////////////////////
-// Originally copied and adapted from EditorProperty, try to keep style in sync.
Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const {
- // `p_text` is expected to be something like this:
- // - `class|Control||Control brief description.`;
- // - `signal|gui_input|(event: InputEvent)|gui_input description.`;
- // - `../../.. :: _on_gui_input()`.
- // Note that the description can be empty or contain `|`.
- PackedStringArray slices = p_text.split("|", true, 3);
- if (slices.size() < 4) {
- return nullptr; // Use default tooltip instead.
- }
-
- String item_type = (slices[0] == "class") ? TTR("Class:") : TTR("Signal:");
- String item_name = slices[1].strip_edges();
- String item_params = slices[2].strip_edges();
- String item_descr = slices[3].strip_edges();
-
- String text = item_type + " [u][b]" + item_name + "[/b][/u]" + item_params + "\n";
- if (item_descr.is_empty()) {
- text += "[i]" + TTR("No description.") + "[/i]";
- } else {
- text += item_descr;
- }
-
- EditorHelpBit *help_bit = memnew(EditorHelpBit);
- help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1));
- help_bit->set_text(text);
-
- return help_bit;
+ // If it's not a doc tooltip, fallback to the default one.
+ return p_text.contains("::") ? nullptr : memnew(EditorHelpTooltip(p_text));
}
struct _ConnectionsDockMethodInfoSort {
@@ -1341,7 +1315,6 @@ void ConnectionsDock::update_tree() {
while (native_base != StringName()) {
String class_name;
String doc_class_name;
- String class_brief;
Ref<Texture2D> class_icon;
List<MethodInfo> class_signals;
@@ -1355,21 +1328,8 @@ void ConnectionsDock::update_tree() {
if (doc_class_name.is_empty()) {
doc_class_name = script_base->get_path().trim_prefix("res://").quote();
}
-
- // For a script class, the cache is filled each time.
- if (!doc_class_name.is_empty()) {
- if (descr_cache.has(doc_class_name)) {
- descr_cache[doc_class_name].clear();
- }
- HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name);
- if (F) {
- class_brief = F->value.brief_description;
- for (int i = 0; i < F->value.signals.size(); i++) {
- descr_cache[doc_class_name][F->value.signals[i].name] = F->value.signals[i].description;
- }
- } else {
- doc_class_name = String();
- }
+ if (!doc_class_name.is_empty() && !doc_data->class_list.find(doc_class_name)) {
+ doc_class_name = String();
}
class_icon = editor_data.get_script_icon(script_base);
@@ -1398,18 +1358,9 @@ void ConnectionsDock::update_tree() {
script_base = base;
} else {
class_name = native_base;
- doc_class_name = class_name;
-
- HashMap<String, DocData::ClassDoc>::ConstIterator F = doc_data->class_list.find(doc_class_name);
- if (F) {
- class_brief = DTR(F->value.brief_description);
- // For a native class, the cache is filled once.
- if (!descr_cache.has(doc_class_name)) {
- for (int i = 0; i < F->value.signals.size(); i++) {
- descr_cache[doc_class_name][F->value.signals[i].name] = DTR(F->value.signals[i].description);
- }
- }
- } else {
+ doc_class_name = native_base;
+
+ if (!doc_data->class_list.find(doc_class_name)) {
doc_class_name = String();
}
@@ -1434,8 +1385,8 @@ void ConnectionsDock::update_tree() {
section_item = tree->create_item(root);
section_item->set_text(0, class_name);
- // `|` separators used in `make_custom_tooltip()` for formatting.
- section_item->set_tooltip_text(0, "class|" + class_name + "||" + class_brief);
+ // `|` separators used in `EditorHelpTooltip` for formatting.
+ section_item->set_tooltip_text(0, "class|" + doc_class_name + "||");
section_item->set_icon(0, class_icon);
section_item->set_selectable(0, false);
section_item->set_editable(0, false);
@@ -1466,22 +1417,8 @@ void ConnectionsDock::update_tree() {
sinfo["args"] = argnames;
signal_item->set_metadata(0, sinfo);
signal_item->set_icon(0, get_editor_theme_icon(SNAME("Signal")));
-
- // Set tooltip with the signal's documentation.
- {
- String descr;
-
- HashMap<StringName, HashMap<StringName, String>>::ConstIterator G = descr_cache.find(doc_class_name);
- if (G) {
- HashMap<StringName, String>::ConstIterator F = G->value.find(signal_name);
- if (F) {
- descr = F->value;
- }
- }
-
- // `|` separators used in `make_custom_tooltip()` for formatting.
- signal_item->set_tooltip_text(0, "signal|" + String(signal_name) + "|" + signame.trim_prefix(mi.name) + "|" + descr);
- }
+ // `|` separators used in `EditorHelpTooltip` for formatting.
+ signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name) + "|" + signame.trim_prefix(mi.name));
// List existing connections.
List<Object::Connection> existing_connections;
diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h
index b07b08ecc7..7316a770ec 100644
--- a/editor/connections_dialog.h
+++ b/editor/connections_dialog.h
@@ -231,8 +231,6 @@ class ConnectionsDock : public VBoxContainer {
PopupMenu *slot_menu = nullptr;
LineEdit *search_box = nullptr;
- HashMap<StringName, HashMap<StringName, String>> descr_cache;
-
void _filter_changed(const String &p_text);
void _make_or_edit_connection();
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index 1c8226e5e1..0e025b4430 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -500,10 +500,11 @@ void CreateDialog::select_type(const String &p_type, bool p_center_on_item) {
to_select->select(0);
search_options->scroll_to_item(to_select, p_center_on_item);
- if (EditorHelp::get_doc_data()->class_list.has(p_type) && !DTR(EditorHelp::get_doc_data()->class_list[p_type].brief_description).is_empty()) {
+ String text = help_bit->get_class_description(p_type);
+ if (!text.is_empty()) {
// Display both class name and description, since the help bit may be displayed
// far away from the location (especially if the dialog was resized to be taller).
- help_bit->set_text(vformat("[b]%s[/b]: %s", p_type, DTR(EditorHelp::get_doc_data()->class_list[p_type].brief_description)));
+ help_bit->set_text(vformat("[b]%s[/b]: %s", p_type, text));
help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
} else {
// Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp
index bca30b2d2d..c3087d797a 100644
--- a/editor/editor_build_profile.cpp
+++ b/editor/editor_build_profile.cpp
@@ -646,24 +646,21 @@ void EditorBuildProfileManager::_class_list_item_selected() {
Variant md = item->get_metadata(0);
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
- String class_name = md;
- String class_description;
-
- DocTools *dd = EditorHelp::get_doc_data();
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_name);
- if (E) {
- class_description = DTR(E->value.brief_description);
+ String text = description_bit->get_class_description(md);
+ if (!text.is_empty()) {
+ // Display both class name and description, since the help bit may be displayed
+ // far away from the location (especially if the dialog was resized to be taller).
+ description_bit->set_text(vformat("[b]%s[/b]: %s", md, text));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
+ } else {
+ // Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
+ description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
}
-
- description_bit->set_text(class_description);
} else if (md.get_type() == Variant::INT) {
- int build_option_id = md;
- String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption(build_option_id));
-
- description_bit->set_text(TTRGET(build_option_description));
- return;
- } else {
- return;
+ String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption((int)md));
+ description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(build_option_description)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
}
}
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index 50406bea6a..5a44ae1aba 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -555,21 +555,22 @@ void EditorFeatureProfileManager::_class_list_item_selected() {
Variant md = item->get_metadata(0);
if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) {
- String class_name = md;
- String class_description;
-
- DocTools *dd = EditorHelp::get_doc_data();
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_name);
- if (E) {
- class_description = DTR(E->value.brief_description);
+ String text = description_bit->get_class_description(md);
+ if (!text.is_empty()) {
+ // Display both class name and description, since the help bit may be displayed
+ // far away from the location (especially if the dialog was resized to be taller).
+ description_bit->set_text(vformat("[b]%s[/b]: %s", md, text));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
+ } else {
+ // Use nested `vformat()` as translators shouldn't interfere with BBCode tags.
+ description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5));
}
-
- description_bit->set_text(class_description);
} else if (md.get_type() == Variant::INT) {
- int feature_id = md;
- String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature(feature_id));
+ String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature((int)md));
+ description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(feature_description)));
+ description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1));
- description_bit->set_text(TTRGET(feature_description));
return;
} else {
return;
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 98c9c58a5f..5d07ba7568 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -38,6 +38,7 @@
#include "doc_data_compressed.gen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
+#include "editor/editor_property_name_processor.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
@@ -2598,7 +2599,7 @@ DocTools *EditorHelp::get_doc_data() {
return doc;
}
-//// EditorHelpBit ///
+/// EditorHelpBit ///
void EditorHelpBit::_go_to_help(String p_what) {
EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT);
@@ -2631,6 +2632,179 @@ void EditorHelpBit::_meta_clicked(String p_select) {
}
}
+String EditorHelpBit::get_class_description(const StringName &p_class_name) const {
+ if (doc_class_cache.has(p_class_name)) {
+ return doc_class_cache[p_class_name];
+ }
+
+ String description;
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
+ if (E) {
+ // Non-native class shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+ description = is_native ? DTR(E->value.brief_description) : E->value.brief_description;
+
+ if (is_native) {
+ doc_class_cache[p_class_name] = description;
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_property_description(const StringName &p_class_name, const StringName &p_property_name) const {
+ if (doc_property_cache.has(p_class_name) && doc_property_cache[p_class_name].has(p_property_name)) {
+ return doc_property_cache[p_class_name][p_property_name];
+ }
+
+ String description;
+ // Non-native properties shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+ DocTools *dd = EditorHelp::get_doc_data();
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name);
+ if (E) {
+ for (int i = 0; i < E->value.properties.size(); i++) {
+ String description_current = is_native ? DTR(E->value.properties[i].description) : E->value.properties[i].description;
+
+ const Vector<String> class_enum = E->value.properties[i].enumeration.split(".");
+ const String enum_name = class_enum.size() >= 2 ? class_enum[1] : "";
+ if (!enum_name.is_empty()) {
+ // Classes can use enums from other classes, so check from which it came.
+ HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(class_enum[0]);
+ if (enum_class) {
+ for (DocData::ConstantDoc val : enum_class->value.constants) {
+ // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector.
+ if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) {
+ const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED);
+ const String enum_prefix = EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " ";
+ const String enum_description = is_native ? DTR(val.description) : val.description;
+
+ // Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value".
+ description_current = description_current.trim_prefix("\n") + vformat("\n[b]%s:[/b] %s", enum_value.trim_prefix(enum_prefix), enum_description.is_empty() ? ("[i]" + DTR("No description available.") + "[/i]") : enum_description);
+ }
+ }
+ }
+ }
+
+ if (E->value.properties[i].name == p_property_name) {
+ description = description_current;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_property_cache[p_class_name][E->value.properties[i].name] = description_current;
+ }
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_method_description(const StringName &p_class_name, const StringName &p_method_name) const {
+ if (doc_method_cache.has(p_class_name) && doc_method_cache[p_class_name].has(p_method_name)) {
+ return doc_method_cache[p_class_name][p_method_name];
+ }
+
+ String description;
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
+ if (E) {
+ // Non-native methods shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+
+ for (int i = 0; i < E->value.methods.size(); i++) {
+ String description_current = is_native ? DTR(E->value.methods[i].description) : E->value.methods[i].description;
+
+ if (E->value.methods[i].name == p_method_name) {
+ description = description_current;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_method_cache[p_class_name][E->value.methods[i].name] = description_current;
+ }
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const {
+ if (doc_signal_cache.has(p_class_name) && doc_signal_cache[p_class_name].has(p_signal_name)) {
+ return doc_signal_cache[p_class_name][p_signal_name];
+ }
+
+ String description;
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name);
+ if (E) {
+ // Non-native signals shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+
+ for (int i = 0; i < E->value.signals.size(); i++) {
+ String description_current = is_native ? DTR(E->value.signals[i].description) : E->value.signals[i].description;
+
+ if (E->value.signals[i].name == p_signal_name) {
+ description = description_current;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_signal_cache[p_class_name][E->value.signals[i].name] = description_current;
+ }
+ }
+ }
+
+ return description;
+}
+
+String EditorHelpBit::get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const {
+ if (doc_theme_item_cache.has(p_class_name) && doc_theme_item_cache[p_class_name].has(p_theme_item_name)) {
+ return doc_theme_item_cache[p_class_name][p_theme_item_name];
+ }
+
+ String description;
+ bool found = false;
+ DocTools *dd = EditorHelp::get_doc_data();
+ HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name);
+ while (E) {
+ // Non-native theme items shouldn't be cached, nor translated.
+ bool is_native = ClassDB::class_exists(p_class_name);
+
+ for (int i = 0; i < E->value.theme_properties.size(); i++) {
+ String description_current = is_native ? DTR(E->value.theme_properties[i].description) : E->value.theme_properties[i].description;
+
+ if (E->value.theme_properties[i].name == p_theme_item_name) {
+ description = description_current;
+ found = true;
+
+ if (!is_native) {
+ break;
+ }
+ }
+
+ if (is_native) {
+ doc_theme_item_cache[p_class_name][E->value.theme_properties[i].name] = description_current;
+ }
+ }
+
+ if (found || E->value.inherits.is_empty()) {
+ break;
+ }
+ // Check for inherited theme items.
+ E = dd->class_list.find(E->value.inherits);
+ }
+
+ return description;
+}
+
void EditorHelpBit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_text", "text"), &EditorHelpBit::set_text);
ADD_SIGNAL(MethodInfo("request_hide"));
@@ -2661,7 +2835,73 @@ EditorHelpBit::EditorHelpBit() {
set_custom_minimum_size(Size2(0, 50 * EDSCALE));
}
-//// FindBar ///
+/// EditorHelpTooltip ///
+
+void EditorHelpTooltip::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_POSTINITIALIZE: {
+ if (!tooltip_text.is_empty()) {
+ parse_tooltip(tooltip_text);
+ }
+ } break;
+ }
+}
+
+// `p_text` is expected to be something like these:
+// - `class|Control||`;
+// - `property|Control|size|`;
+// - `signal|Control|gui_input|(event: InputEvent)`
+void EditorHelpTooltip::parse_tooltip(const String &p_text) {
+ tooltip_text = p_text;
+
+ PackedStringArray slices = p_text.split("|", true, 3);
+ ERR_FAIL_COND_MSG(slices.size() < 4, "Invalid tooltip formatting. The expect string should be formatted as 'type|class|property|args'.");
+
+ String type = slices[0];
+ String class_name = slices[1];
+ String property_name = slices[2];
+ String property_args = slices[3];
+
+ String title;
+ String description;
+ String formatted_text;
+
+ if (type == "class") {
+ title = class_name;
+ description = get_class_description(class_name);
+ formatted_text = TTR("Class:");
+ } else {
+ title = property_name;
+
+ if (type == "property") {
+ description = get_property_description(class_name, property_name);
+ formatted_text = TTR("Property:");
+ } else if (type == "method") {
+ description = get_method_description(class_name, property_name);
+ formatted_text = TTR("Method:");
+ } else if (type == "signal") {
+ description = get_signal_description(class_name, property_name);
+ formatted_text = TTR("Signal:");
+ } else if (type == "theme_item") {
+ description = get_theme_item_description(class_name, property_name);
+ formatted_text = TTR("Theme Item:");
+ } else {
+ ERR_FAIL_MSG("Invalid tooltip type '" + type + "'. Valid types are 'class', 'property', 'method', 'signal', and 'theme_item'.");
+ }
+ }
+
+ formatted_text += " [u][b]" + title + "[/b][/u]" + property_args + "\n";
+ formatted_text += description.is_empty() ? "[i]" + TTR("No description available.") + "[/i]" : description;
+ set_text(formatted_text);
+}
+
+EditorHelpTooltip::EditorHelpTooltip(const String &p_text) {
+ tooltip_text = p_text;
+
+ get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0));
+}
+
+/// FindBar ///
FindBar::FindBar() {
search_text = memnew(LineEdit);
diff --git a/editor/editor_help.h b/editor/editor_help.h
index 3824625436..0ca3942e0b 100644
--- a/editor/editor_help.h
+++ b/editor/editor_help.h
@@ -232,6 +232,12 @@ public:
class EditorHelpBit : public MarginContainer {
GDCLASS(EditorHelpBit, MarginContainer);
+ inline static HashMap<StringName, String> doc_class_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_property_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_method_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_signal_cache;
+ inline static HashMap<StringName, HashMap<StringName, String>> doc_theme_item_cache;
+
RichTextLabel *rich_text = nullptr;
void _go_to_help(String p_what);
void _meta_clicked(String p_select);
@@ -243,9 +249,30 @@ protected:
void _notification(int p_what);
public:
+ String get_class_description(const StringName &p_class_name) const;
+ String get_property_description(const StringName &p_class_name, const StringName &p_property_name) const;
+ String get_method_description(const StringName &p_class_name, const StringName &p_method_name) const;
+ String get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const;
+ String get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const;
+
RichTextLabel *get_rich_text() { return rich_text; }
void set_text(const String &p_text);
+
EditorHelpBit();
};
+class EditorHelpTooltip : public EditorHelpBit {
+ GDCLASS(EditorHelpTooltip, EditorHelpBit);
+
+ String tooltip_text;
+
+protected:
+ void _notification(int p_what);
+
+public:
+ void parse_tooltip(const String &p_text);
+
+ EditorHelpTooltip(const String &p_text = String());
+};
+
#endif // EDITOR_HELP_H
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 382b182e0e..91a3181747 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -905,47 +905,17 @@ void EditorProperty::_update_pin_flags() {
}
}
-static Control *make_help_bit(const String &p_item_type, const String &p_text, const String &p_warning, const Color &p_warn_color) {
- // `p_text` is expected to be something like this:
- // `item_name|Item description.`.
- // Note that the description can be empty or contain `|`.
- PackedStringArray slices = p_text.split("|", true, 1);
- if (slices.size() < 2) {
- return nullptr; // Use default tooltip instead.
- }
-
- String item_name = slices[0].strip_edges();
- String item_descr = slices[1].strip_edges();
-
- String text;
- if (!p_item_type.is_empty()) {
- text = p_item_type + " ";
- }
- text += "[u][b]" + item_name + "[/b][/u]\n";
- if (item_descr.is_empty()) {
- text += "[i]" + TTR("No description.") + "[/i]";
- } else {
- text += item_descr;
- }
- if (!p_warning.is_empty()) {
- text += "\n[b][color=" + p_warn_color.to_html(false) + "]" + p_warning + "[/color][/b]";
- }
-
- EditorHelpBit *help_bit = memnew(EditorHelpBit);
- help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1));
- help_bit->set_text(text);
-
- return help_bit;
-}
-
Control *EditorProperty::make_custom_tooltip(const String &p_text) const {
- String warn;
- Color warn_color;
+ EditorHelpTooltip *tooltip = memnew(EditorHelpTooltip(p_text));
+
if (object->has_method("_get_property_warning")) {
- warn = object->call("_get_property_warning", property);
- warn_color = get_theme_color(SNAME("warning_color"));
+ String warn = object->call("_get_property_warning", property);
+ if (!warn.is_empty()) {
+ tooltip->set_text(tooltip->get_rich_text()->get_text() + "\n[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + warn + "[/color][/b]");
+ }
}
- return make_help_bit(TTR("Property:"), p_text, warn, warn_color);
+
+ return tooltip;
}
void EditorProperty::menu_option(int p_option) {
@@ -1178,7 +1148,8 @@ void EditorInspectorCategory::_notification(int p_what) {
}
Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const {
- return make_help_bit(TTR("Class:"), p_text, String(), Color());
+ // Far from perfect solution, as there's nothing that prevents a category from having a name that starts with that.
+ return p_text.begins_with("class|") ? memnew(EditorHelpTooltip(p_text)) : nullptr;
}
Size2 EditorInspectorCategory::get_minimum_size() const {
@@ -2883,24 +2854,8 @@ void EditorInspector::update_tree() {
category->doc_class_name = doc_name;
if (use_doc_hints) {
- String descr = "";
- // Sets the category tooltip to show documentation.
- if (!class_descr_cache.has(doc_name)) {
- DocTools *dd = EditorHelp::get_doc_data();
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(doc_name);
- if (E) {
- descr = E->value.brief_description;
- }
- if (ClassDB::class_exists(doc_name)) {
- descr = DTR(descr); // Do not translate the class description of scripts.
- class_descr_cache[doc_name] = descr; // Do not cache the class description of scripts.
- }
- } else {
- descr = class_descr_cache[doc_name];
- }
-
- // `|` separator used in `make_help_bit()` for formatting.
- category->set_tooltip_text(p.name + "|" + descr);
+ // `|` separator used in `EditorHelpTooltip` for formatting.
+ category->set_tooltip_text("class|" + doc_name + "||");
}
// Add editors at the start of a category.
@@ -3195,13 +3150,12 @@ void EditorInspector::update_tree() {
restart_request_props.insert(p.name);
}
- PropertyDocInfo doc_info;
+ String doc_path;
+ String theme_item_name;
+ StringName classname = doc_name;
+ // Build the doc hint, to use as tooltip.
if (use_doc_hints) {
- // Build the doc hint, to use as tooltip.
-
- // Get the class name.
- StringName classname = doc_name;
if (!object_class.is_empty()) {
classname = object_class;
} else if (Object::cast_to<MultiNodeEdit>(object)) {
@@ -3231,83 +3185,55 @@ void EditorInspector::update_tree() {
classname = get_edited_object()->get_class();
}
- // Search for the property description in the cache.
- HashMap<StringName, HashMap<StringName, PropertyDocInfo>>::Iterator E = doc_info_cache.find(classname);
+ // Search for the doc path in the cache.
+ HashMap<StringName, HashMap<StringName, String>>::Iterator E = doc_path_cache.find(classname);
if (E) {
- HashMap<StringName, PropertyDocInfo>::Iterator F = E->value.find(propname);
+ HashMap<StringName, String>::Iterator F = E->value.find(propname);
if (F) {
found = true;
- doc_info = F->value;
+ doc_path = F->value;
}
}
if (!found) {
+ DocTools *dd = EditorHelp::get_doc_data();
+ // Do not cache the doc path information of scripts.
bool is_native_class = ClassDB::class_exists(classname);
- // Build the property description String and add it to the cache.
- DocTools *dd = EditorHelp::get_doc_data();
HashMap<String, DocData::ClassDoc>::ConstIterator F = dd->class_list.find(classname);
- while (F && doc_info.description.is_empty()) {
- for (int i = 0; i < F->value.properties.size(); i++) {
- if (F->value.properties[i].name == propname.operator String()) {
- doc_info.description = F->value.properties[i].description;
- if (is_native_class) {
- doc_info.description = DTR(doc_info.description); // Do not translate the property description of scripts.
- }
-
- const Vector<String> class_enum = F->value.properties[i].enumeration.split(".");
- const String class_name = class_enum[0];
- const String enum_name = class_enum.size() >= 2 ? class_enum[1] : "";
- if (!enum_name.is_empty()) {
- HashMap<String, DocData::ClassDoc>::ConstIterator enum_class = dd->class_list.find(class_name);
- if (enum_class) {
- for (DocData::ConstantDoc val : enum_class->value.constants) {
- // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector.
- if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) {
- const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED);
- // Prettify the enum value display, so that "<ENUM NAME>_<VALUE>" becomes "Value".
- String desc = val.description;
- if (is_native_class) {
- desc = DTR(desc); // Do not translate the enum value description of scripts.
- }
- desc = desc.trim_prefix("\n");
- doc_info.description += vformat(
- "\n[b]%s:[/b] %s",
- enum_value.trim_prefix(EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " "),
- desc.is_empty() ? ("[i]" + TTR("No description.") + "[/i]") : desc);
- }
- }
- }
- }
-
- doc_info.path = "class_property:" + F->value.name + ":" + F->value.properties[i].name;
- break;
- }
- }
-
+ while (F) {
Vector<String> slices = propname.operator String().split("/");
+ // Check if it's a theme item first.
if (slices.size() == 2 && slices[0].begins_with("theme_override_")) {
for (int i = 0; i < F->value.theme_properties.size(); i++) {
+ String doc_path_current = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name;
if (F->value.theme_properties[i].name == slices[1]) {
- doc_info.description = F->value.theme_properties[i].description;
- if (is_native_class) {
- doc_info.description = DTR(doc_info.description); // Do not translate the theme item description of scripts.
- }
- doc_info.path = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name;
- break;
+ doc_path = doc_path_current;
+ theme_item_name = F->value.theme_properties[i].name;
}
}
- }
- if (!F->value.inherits.is_empty()) {
- F = dd->class_list.find(F->value.inherits);
+ if (is_native_class) {
+ doc_path_cache[classname][propname] = doc_path;
+ }
} else {
- break;
+ for (int i = 0; i < F->value.properties.size(); i++) {
+ String doc_path_current = "class_property:" + F->value.name + ":" + F->value.properties[i].name;
+ if (F->value.properties[i].name == propname.operator String()) {
+ doc_path = doc_path_current;
+ }
+
+ if (is_native_class) {
+ doc_path_cache[classname][propname] = doc_path;
+ }
+ }
}
- }
- if (is_native_class) {
- doc_info_cache[classname][propname] = doc_info; // Do not cache the doc information of scripts.
+ if (!doc_path.is_empty() || F->value.inherits.is_empty()) {
+ break;
+ }
+ // Couldn't find the doc path in the class itself, try its super class.
+ F = dd->class_list.find(F->value.inherits);
}
}
}
@@ -3346,11 +3272,11 @@ void EditorInspector::update_tree() {
if (properties.size()) {
if (properties.size() == 1) {
- //since it's one, associate:
+ // Since it's one, associate:
ep->property = properties[0];
ep->property_path = property_prefix + properties[0];
ep->property_usage = p.usage;
- //and set label?
+ // And set label?
}
if (!editors[i].label.is_empty()) {
ep->set_label(editors[i].label);
@@ -3398,9 +3324,17 @@ void EditorInspector::update_tree() {
ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));
ep->connect("resource_selected", callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED);
ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED);
- // `|` separator used in `make_help_bit()` for formatting.
- ep->set_tooltip_text(property_prefix + p.name + "|" + doc_info.description);
- ep->set_doc_path(doc_info.path);
+
+ if (use_doc_hints) {
+ // `|` separator used in `EditorHelpTooltip` for formatting.
+ if (theme_item_name.is_empty()) {
+ ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name + "|");
+ } else {
+ ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name + "|");
+ }
+ }
+
+ ep->set_doc_path(doc_path);
ep->update_property();
ep->_update_pin_flags();
ep->update_editor_property_status();
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index 4393922f52..b5f0cec80b 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -501,13 +501,7 @@ class EditorInspector : public ScrollContainer {
int property_focusable;
int update_scroll_request;
- struct PropertyDocInfo {
- String description;
- String path;
- };
-
- HashMap<StringName, HashMap<StringName, PropertyDocInfo>> doc_info_cache;
- HashMap<StringName, String> class_descr_cache;
+ HashMap<StringName, HashMap<StringName, String>> doc_path_cache;
HashSet<StringName> restart_request_props;
HashMap<ObjectID, int> scroll_cache;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index e69dcb2278..0037b356d0 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -4473,7 +4473,7 @@ String EditorNode::_get_system_info() const {
}
if (driver_name == "vulkan") {
driver_name = "Vulkan";
- } else if (driver_name == "opengl3") {
+ } else if (driver_name.begins_with("opengl3")) {
driver_name = "GLES3";
}
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index a197eb9dce..0be23fa3cc 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -47,6 +47,7 @@
#include "editor/plugins/script_editor_plugin.h"
#include "editor/project_settings_editor.h"
#include "editor/property_selector.h"
+#include "editor/scene_tree_dock.h"
#include "scene/2d/gpu_particles_2d.h"
#include "scene/3d/fog_volume.h"
#include "scene/3d/gpu_particles_3d.h"
@@ -2769,7 +2770,7 @@ EditorPropertyColor::EditorPropertyColor() {
void EditorPropertyNodePath::_set_read_only(bool p_read_only) {
assign->set_disabled(p_read_only);
- clear->set_disabled(p_read_only);
+ menu->set_disabled(p_read_only);
};
Variant EditorPropertyNodePath::_get_cache_value(const StringName &p_prop, bool &r_valid) const {
@@ -2817,9 +2818,79 @@ void EditorPropertyNodePath::_node_assign() {
scene_tree->popup_scenetree_dialog();
}
-void EditorPropertyNodePath::_node_clear() {
- emit_changed(get_edited_property(), Variant());
- update_property();
+void EditorPropertyNodePath::_update_menu() {
+ const NodePath &np = _get_node_path();
+
+ menu->get_popup()->set_item_disabled(ACTION_CLEAR, np.is_empty());
+ menu->get_popup()->set_item_disabled(ACTION_COPY, np.is_empty());
+
+ Node *edited_node = Object::cast_to<Node>(get_edited_object());
+ menu->get_popup()->set_item_disabled(ACTION_SELECT, !edited_node || !edited_node->has_node(np));
+}
+
+void EditorPropertyNodePath::_menu_option(int p_idx) {
+ switch (p_idx) {
+ case ACTION_CLEAR: {
+ emit_changed(get_edited_property(), NodePath());
+ update_property();
+ } break;
+
+ case ACTION_COPY: {
+ DisplayServer::get_singleton()->clipboard_set(_get_node_path());
+ } break;
+
+ case ACTION_EDIT: {
+ assign->hide();
+ menu->hide();
+
+ const NodePath &np = _get_node_path();
+ edit->set_text(np);
+ edit->show();
+ callable_mp((Control *)edit, &Control::grab_focus).call_deferred();
+ } break;
+
+ case ACTION_SELECT: {
+ const Node *edited_node = get_base_node();
+ ERR_FAIL_NULL(edited_node);
+
+ const NodePath &np = _get_node_path();
+ Node *target_node = edited_node->get_node_or_null(np);
+ ERR_FAIL_NULL(target_node);
+
+ SceneTreeDock::get_singleton()->set_selected(target_node);
+ } break;
+ }
+}
+
+void EditorPropertyNodePath::_accept_text() {
+ _text_submitted(edit->get_text());
+}
+
+void EditorPropertyNodePath::_text_submitted(const String &p_text) {
+ NodePath np = p_text;
+ emit_changed(get_edited_property(), np);
+ edit->hide();
+ assign->show();
+ menu->show();
+}
+
+const NodePath EditorPropertyNodePath::_get_node_path() const {
+ const Node *base_node = const_cast<EditorPropertyNodePath *>(this)->get_base_node();
+
+ Variant val = get_edited_property_value();
+ Node *n = Object::cast_to<Node>(val);
+ if (n) {
+ if (!n->is_inside_tree()) {
+ return NodePath();
+ }
+ if (base_node) {
+ return base_node->get_path_to(n);
+ } else {
+ return get_tree()->get_edited_scene_root()->get_path_to(n);
+ }
+ } else {
+ return val;
+ }
}
bool EditorPropertyNodePath::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
@@ -2865,26 +2936,11 @@ bool EditorPropertyNodePath::is_drop_valid(const Dictionary &p_drag_data) const
}
void EditorPropertyNodePath::update_property() {
- Node *base_node = get_base_node();
-
- NodePath p;
- Variant val = get_edited_object()->get(get_edited_property());
- Node *n = Object::cast_to<Node>(val);
- if (n) {
- if (!n->is_inside_tree()) {
- return;
- }
- if (base_node) {
- p = base_node->get_path_to(n);
- } else {
- p = get_tree()->get_edited_scene_root()->get_path_to(n);
- }
- } else {
- p = get_edited_property_value();
- }
-
+ const Node *base_node = get_base_node();
+ const NodePath &p = _get_node_path();
assign->set_tooltip_text(p);
- if (p == NodePath()) {
+
+ if (p.is_empty()) {
assign->set_icon(Ref<Texture2D>());
assign->set_text(TTR("Assign..."));
assign->set_flat(false);
@@ -2898,7 +2954,7 @@ void EditorPropertyNodePath::update_property() {
return;
}
- Node *target_node = base_node->get_node(p);
+ const Node *target_node = base_node->get_node(p);
ERR_FAIL_NULL(target_node);
if (String(target_node->get_name()).contains("@")) {
@@ -2922,14 +2978,15 @@ void EditorPropertyNodePath::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
- Ref<Texture2D> t = get_editor_theme_icon(SNAME("Clear"));
- clear->set_icon(t);
+ menu->set_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
+ menu->get_popup()->set_item_icon(ACTION_CLEAR, get_editor_theme_icon(SNAME("Clear")));
+ menu->get_popup()->set_item_icon(ACTION_COPY, get_editor_theme_icon(SNAME("ActionCopy")));
+ menu->get_popup()->set_item_icon(ACTION_EDIT, get_editor_theme_icon(SNAME("Edit")));
+ menu->get_popup()->set_item_icon(ACTION_SELECT, get_editor_theme_icon(SNAME("ExternalLink")));
} break;
}
}
-void EditorPropertyNodePath::_bind_methods() {
-}
Node *EditorPropertyNodePath::get_base_node() {
if (!base_hint.is_empty() && get_tree()->get_root()->has_node(base_hint)) {
return get_tree()->get_root()->get_node(base_hint);
@@ -2969,16 +3026,28 @@ EditorPropertyNodePath::EditorPropertyNodePath() {
assign->set_h_size_flags(SIZE_EXPAND_FILL);
assign->set_clip_text(true);
assign->set_auto_translate(false);
+ assign->set_expand_icon(true);
assign->connect("pressed", callable_mp(this, &EditorPropertyNodePath::_node_assign));
SET_DRAG_FORWARDING_CD(assign, EditorPropertyNodePath);
hbc->add_child(assign);
- clear = memnew(Button);
- clear->set_flat(true);
- clear->connect("pressed", callable_mp(this, &EditorPropertyNodePath::_node_clear));
- hbc->add_child(clear);
-
- scene_tree = nullptr; //do not allocate unnecessarily
+ menu = memnew(MenuButton);
+ menu->set_flat(true);
+ menu->connect(SNAME("about_to_popup"), callable_mp(this, &EditorPropertyNodePath::_update_menu));
+ hbc->add_child(menu);
+
+ menu->get_popup()->add_item(TTR("Clear"), ACTION_CLEAR);
+ menu->get_popup()->add_item(TTR("Copy as Text"), ACTION_COPY);
+ menu->get_popup()->add_item(TTR("Edit"), ACTION_EDIT);
+ menu->get_popup()->add_item(TTR("Show Node in Tree"), ACTION_SELECT);
+ menu->get_popup()->connect(SNAME("id_pressed"), callable_mp(this, &EditorPropertyNodePath::_menu_option));
+
+ edit = memnew(LineEdit);
+ edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ edit->hide();
+ edit->connect(SNAME("focus_exited"), callable_mp(this, &EditorPropertyNodePath::_accept_text));
+ edit->connect(SNAME("text_submitted"), callable_mp(this, &EditorPropertyNodePath::_text_submitted));
+ hbc->add_child(edit);
}
///////////////////// RID /////////////////////////
diff --git a/editor/editor_properties.h b/editor/editor_properties.h
index 5feb40b3d7..ff9d47627a 100644
--- a/editor/editor_properties.h
+++ b/editor/editor_properties.h
@@ -40,6 +40,7 @@ class EditorFileDialog;
class EditorLocaleDialog;
class EditorResourcePicker;
class EditorSpinSlider;
+class MenuButton;
class PropertySelector;
class SceneTreeDialog;
class TextEdit;
@@ -649,8 +650,18 @@ public:
class EditorPropertyNodePath : public EditorProperty {
GDCLASS(EditorPropertyNodePath, EditorProperty);
+
+ enum {
+ ACTION_CLEAR,
+ ACTION_COPY,
+ ACTION_EDIT,
+ ACTION_SELECT,
+ };
+
Button *assign = nullptr;
- Button *clear = nullptr;
+ MenuButton *menu = nullptr;
+ LineEdit *edit = nullptr;
+
SceneTreeDialog *scene_tree = nullptr;
NodePath base_hint;
bool use_path_from_scene_root = false;
@@ -659,8 +670,12 @@ class EditorPropertyNodePath : public EditorProperty {
Vector<StringName> valid_types;
void _node_selected(const NodePath &p_path);
void _node_assign();
- void _node_clear();
Node *get_base_node();
+ void _update_menu();
+ void _menu_option(int p_idx);
+ void _accept_text();
+ void _text_submitted(const String &p_text);
+ const NodePath _get_node_path() const;
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
@@ -670,7 +685,6 @@ class EditorPropertyNodePath : public EditorProperty {
protected:
virtual void _set_read_only(bool p_read_only) override;
- static void _bind_methods();
void _notification(int p_what);
public:
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index 950a1e1c4d..3fad85c95c 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -255,7 +255,7 @@ void EditorPropertyArray::update_property() {
array_type_name = vformat("%s[%s]", array_type_name, type_name);
}
- if (array.get_type() == Variant::NIL) {
+ if (!array.is_array()) {
edit->set_text(vformat(TTR("(Nil) %s"), array_type_name));
edit->set_pressed(false);
if (container) {
@@ -287,6 +287,7 @@ void EditorPropertyArray::update_property() {
if (!container) {
container = memnew(MarginContainer);
container->set_theme_type_variation("MarginContainer4px");
+ container->set_mouse_filter(MOUSE_FILTER_STOP);
add_child(container);
set_bottom_editor(container);
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 6c41e34e51..c492589e63 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -859,16 +859,20 @@ Vector<String> EditorExportPlatform::get_forced_export_files() {
bool use_data = GLOBAL_GET("internationalization/locale/include_text_server_data");
if (use_data) {
// Try using user provided data file.
- String ts_data = "res://" + TS->get_support_data_filename();
- if (FileAccess::exists(ts_data)) {
- files.push_back(ts_data);
- } else {
- // Use default text server data.
- String icu_data_file = EditorPaths::get_singleton()->get_cache_dir().path_join("tmp_icu_data");
- ERR_FAIL_COND_V(!TS->save_support_data(icu_data_file), files);
- files.push_back(icu_data_file);
- // Remove the file later.
- MessageQueue::get_singleton()->push_callable(callable_mp_static(DirAccess::remove_absolute), icu_data_file);
+ if (!TS->get_support_data_filename().is_empty()) {
+ String ts_data = "res://" + TS->get_support_data_filename();
+ if (FileAccess::exists(ts_data)) {
+ files.push_back(ts_data);
+ } else {
+ // Use default text server data.
+ String abs_path = ProjectSettings::get_singleton()->globalize_path(ts_data);
+ ERR_FAIL_COND_V(!TS->save_support_data(abs_path), files);
+ if (FileAccess::exists(abs_path)) {
+ files.push_back(ts_data);
+ // Remove the file later.
+ MessageQueue::get_singleton()->push_callable(callable_mp_static(DirAccess::remove_absolute), abs_path);
+ }
+ }
}
}
}
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 36f712dde8..be06a3932c 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -216,7 +216,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
subdirectory_item->set_icon(0, get_editor_theme_icon(SNAME("Folder")));
subdirectory_item->set_selectable(0, true);
subdirectory_item->set_metadata(0, lpath);
- if (!p_select_in_favorites && (current_path == lpath || ((display_mode == DISPLAY_MODE_SPLIT) && current_path.get_base_dir() == lpath))) {
+ if (!p_select_in_favorites && (current_path == lpath || ((display_mode != DISPLAY_MODE_TREE_ONLY) && current_path.get_base_dir() == lpath))) {
subdirectory_item->select(0);
// Keep select an item when re-created a tree
// To prevent crashing when nothing is selected.
@@ -302,7 +302,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
udata.push_back(file_item);
EditorResourcePreview::get_singleton()->queue_resource_preview(file_metadata, this, "_tree_thumbnail_done", udata);
}
- } else if (display_mode == DISPLAY_MODE_SPLIT) {
+ } else {
if (lpath.get_base_dir() == current_path.get_base_dir()) {
subdirectory_item->select(0);
subdirectory_item->set_as_cursor(0);
@@ -455,22 +455,29 @@ void FileSystemDock::set_display_mode(DisplayMode p_display_mode) {
void FileSystemDock::_update_display_mode(bool p_force) {
// Compute the new display mode.
if (p_force || old_display_mode != display_mode) {
- button_toggle_display_mode->set_pressed(display_mode == DISPLAY_MODE_SPLIT);
switch (display_mode) {
case DISPLAY_MODE_TREE_ONLY:
+ button_toggle_display_mode->set_icon(get_editor_theme_icon(SNAME("Panels1")));
tree->show();
tree->set_v_size_flags(SIZE_EXPAND_FILL);
- if (display_mode == DISPLAY_MODE_TREE_ONLY) {
- toolbar2_hbc->show();
- } else {
- toolbar2_hbc->hide();
- }
+ toolbar2_hbc->show();
_update_tree(get_uncollapsed_paths());
file_list_vb->hide();
break;
- case DISPLAY_MODE_SPLIT:
+ case DISPLAY_MODE_HSPLIT:
+ case DISPLAY_MODE_VSPLIT:
+ const bool is_vertical = display_mode == DISPLAY_MODE_VSPLIT;
+ const int split_offset = split_box->get_split_offset();
+ is_vertical ? split_box_offset_h = split_offset : split_box_offset_v = split_offset;
+ split_box->set_vertical(is_vertical);
+
+ const int actual_offset = is_vertical ? split_box_offset_v : split_box_offset_h;
+ split_box->set_split_offset(actual_offset);
+ const StringName icon = is_vertical ? SNAME("Panels2") : SNAME("Panels2Alt");
+ button_toggle_display_mode->set_icon(get_editor_theme_icon(icon));
+
tree->show();
tree->set_v_size_flags(SIZE_EXPAND_FILL);
tree->ensure_cursor_is_visible();
@@ -500,7 +507,6 @@ void FileSystemDock::_notification(int p_what) {
EditorResourcePreview::get_singleton()->connect("preview_invalidated", callable_mp(this, &FileSystemDock::_preview_invalidated));
button_reload->set_icon(get_editor_theme_icon(SNAME("Reload")));
- button_toggle_display_mode->set_icon(get_editor_theme_icon(SNAME("Panels2")));
button_file_list_display_mode->connect("pressed", callable_mp(this, &FileSystemDock::_toggle_file_display));
files->connect("item_activated", callable_mp(this, &FileSystemDock::_file_list_activate_file));
@@ -587,7 +593,15 @@ void FileSystemDock::_notification(int p_what) {
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
// Update icons.
button_reload->set_icon(get_editor_theme_icon(SNAME("Reload")));
- button_toggle_display_mode->set_icon(get_editor_theme_icon(SNAME("Panels2")));
+
+ StringName mode_icon = "Panels1";
+ if (display_mode == DISPLAY_MODE_VSPLIT) {
+ mode_icon = "Panels2";
+ } else if (display_mode == DISPLAY_MODE_HSPLIT) {
+ mode_icon = "Panels2Alt";
+ }
+ button_toggle_display_mode->set_icon(get_editor_theme_icon(mode_icon));
+
if (is_layout_rtl()) {
button_hist_next->set_icon(get_editor_theme_icon(SNAME("Back")));
button_hist_prev->set_icon(get_editor_theme_icon(SNAME("Forward")));
@@ -665,7 +679,7 @@ void FileSystemDock::_tree_multi_selected(Object *p_item, int p_column, bool p_s
_push_to_history();
// Update the file list.
- if (!updating_tree && display_mode == DISPLAY_MODE_SPLIT) {
+ if (!updating_tree && display_mode != DISPLAY_MODE_TREE_ONLY) {
_update_file_list(false);
}
}
@@ -717,7 +731,7 @@ void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_fa
_push_to_history();
_update_tree(get_uncollapsed_paths(), false, p_select_in_favorites, true);
- if (display_mode == DISPLAY_MODE_SPLIT) {
+ if (display_mode != DISPLAY_MODE_TREE_ONLY) {
_update_file_list(false);
files->get_v_scroll_bar()->set_value(0);
}
@@ -2411,7 +2425,8 @@ void FileSystemDock::_search_changed(const String &p_text, const Control *p_from
case DISPLAY_MODE_TREE_ONLY: {
_update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path);
} break;
- case DISPLAY_MODE_SPLIT: {
+ case DISPLAY_MODE_HSPLIT:
+ case DISPLAY_MODE_VSPLIT: {
_update_file_list(false);
_update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path);
} break;
@@ -2423,8 +2438,15 @@ void FileSystemDock::_rescan() {
EditorFileSystem::get_singleton()->scan();
}
-void FileSystemDock::_toggle_split_mode(bool p_active) {
- set_display_mode(p_active ? DISPLAY_MODE_SPLIT : DISPLAY_MODE_TREE_ONLY);
+void FileSystemDock::_change_split_mode() {
+ DisplayMode next_mode = DISPLAY_MODE_TREE_ONLY;
+ if (display_mode == DISPLAY_MODE_VSPLIT) {
+ next_mode = DISPLAY_MODE_HSPLIT;
+ } else if (display_mode == DISPLAY_MODE_TREE_ONLY) {
+ next_mode = DISPLAY_MODE_VSPLIT;
+ }
+
+ set_display_mode(next_mode);
emit_signal(SNAME("display_mode_changed"));
}
@@ -2436,7 +2458,7 @@ void FileSystemDock::focus_on_filter() {
LineEdit *current_search_box = nullptr;
if (display_mode == DISPLAY_MODE_TREE_ONLY) {
current_search_box = tree_search_box;
- } else if (display_mode == DISPLAY_MODE_SPLIT) {
+ } else {
current_search_box = file_list_search_box;
}
@@ -2677,7 +2699,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data,
EditorSettings::get_singleton()->set_favorites(dirs);
_update_tree(get_uncollapsed_paths());
- if (display_mode == DISPLAY_MODE_SPLIT && current_path == "Favorites") {
+ if (display_mode != DISPLAY_MODE_TREE_ONLY && current_path == "Favorites") {
_update_file_list(true);
}
return;
@@ -3152,7 +3174,7 @@ void FileSystemDock::_file_multi_selected(int p_index, bool p_selected) {
String fpath = files->get_item_metadata(current);
if (!fpath.ends_with("/")) {
current_path = fpath;
- if (display_mode == DISPLAY_MODE_SPLIT) {
+ if (display_mode != DISPLAY_MODE_TREE_ONLY) {
_update_tree(get_uncollapsed_paths());
}
}
@@ -3225,7 +3247,7 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) {
}
item->select(0);
- if (display_mode == DisplayMode::DISPLAY_MODE_SPLIT) {
+ if (display_mode != DisplayMode::DISPLAY_MODE_TREE_ONLY) {
files->deselect_all();
// Try to select the corresponding file list item.
const int files_item_idx = files->find_metadata(fpath);
@@ -3559,10 +3581,9 @@ FileSystemDock::FileSystemDock() {
toolbar_hbc->add_child(button_reload);
button_toggle_display_mode = memnew(Button);
- button_toggle_display_mode->set_toggle_mode(true);
- button_toggle_display_mode->connect("toggled", callable_mp(this, &FileSystemDock::_toggle_split_mode));
+ button_toggle_display_mode->connect("pressed", callable_mp(this, &FileSystemDock::_change_split_mode));
button_toggle_display_mode->set_focus_mode(FOCUS_NONE);
- button_toggle_display_mode->set_tooltip_text(TTR("Toggle Split Mode"));
+ button_toggle_display_mode->set_tooltip_text(TTR("Change Split Mode"));
button_toggle_display_mode->set_flat(true);
toolbar_hbc->add_child(button_toggle_display_mode);
@@ -3587,7 +3608,7 @@ FileSystemDock::FileSystemDock() {
add_child(tree_popup);
- split_box = memnew(VSplitContainer);
+ split_box = memnew(SplitContainer);
split_box->set_v_size_flags(SIZE_EXPAND_FILL);
add_child(split_box);
@@ -3597,7 +3618,7 @@ FileSystemDock::FileSystemDock() {
SET_DRAG_FORWARDING_GCD(tree, FileSystemDock);
tree->set_allow_rmb_select(true);
tree->set_select_mode(Tree::SELECT_MULTI);
- tree->set_custom_minimum_size(Size2(0, 15 * EDSCALE));
+ tree->set_custom_minimum_size(Size2(40 * EDSCALE, 15 * EDSCALE));
tree->set_column_clip_content(0, true);
split_box->add_child(tree);
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 818b91bdb9..1e04b6a4ff 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -88,7 +88,8 @@ public:
enum DisplayMode {
DISPLAY_MODE_TREE_ONLY,
- DISPLAY_MODE_SPLIT,
+ DISPLAY_MODE_VSPLIT,
+ DISPLAY_MODE_HSPLIT,
};
enum FileSortOption {
@@ -144,9 +145,12 @@ private:
VBoxContainer *scanning_vb = nullptr;
ProgressBar *scanning_progress = nullptr;
- VSplitContainer *split_box = nullptr;
+ SplitContainer *split_box = nullptr;
VBoxContainer *file_list_vb = nullptr;
+ int split_box_offset_h = 0;
+ int split_box_offset_v = 0;
+
HashSet<String> favorites;
Button *button_toggle_display_mode = nullptr;
@@ -299,7 +303,7 @@ private:
void _set_scanning_mode();
void _rescan();
- void _toggle_split_mode(bool p_active);
+ void _change_split_mode();
void _search_changed(const String &p_text, const Control *p_from);
diff --git a/editor/icons/CodeRegionFoldDownArrow.svg b/editor/icons/CodeRegionFoldDownArrow.svg
index 3bc4f3f73b..744ea7197d 100644
--- a/editor/icons/CodeRegionFoldDownArrow.svg
+++ b/editor/icons/CodeRegionFoldDownArrow.svg
@@ -1 +1 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm1 5a1 1 0 0 1 1.414-1.414L6 6.172l1.586-1.586A1 1 0 0 1 9 6L6.707 8.293a1 1 0 0 1-1.414 0Z" fill="#fff"/></svg> \ No newline at end of file
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M10 3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1zM3 5.75a1 1 0 0 1 1.414-1.414L6 5.922l1.586-1.586A1 1 0 0 1 9 5.75L6.707 8.043a1 1 0 0 1-1.414 0z" fill="#fff"/></svg>
diff --git a/editor/icons/CodeRegionFoldedRightArrow.svg b/editor/icons/CodeRegionFoldedRightArrow.svg
index a9b81d54f3..245371b5a1 100644
--- a/editor/icons/CodeRegionFoldedRightArrow.svg
+++ b/editor/icons/CodeRegionFoldedRightArrow.svg
@@ -1 +1 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm3.5 8a1 1 0 0 1-1.414-1.414L5.672 6 4.086 4.414A1 1 0 0 1 5.5 3l2.293 2.293a1 1 0 0 1 0 1.414Z" fill="#fff"/></svg> \ No newline at end of file
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M3 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1zm2.75 7a1 1 0 0 1-1.414-1.414L5.922 6 4.336 4.414A1 1 0 0 1 5.75 3l2.293 2.293a1 1 0 0 1 0 1.414z" fill="#fff"/></svg>
diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp
index d13c0e7f86..5996b459a5 100644
--- a/editor/import_dock.cpp
+++ b/editor/import_dock.cpp
@@ -499,9 +499,12 @@ void ImportDock::_reimport_attempt() {
String imported_with = config->get_value("remap", "importer");
if (imported_with != importer_name) {
- need_cleanup.push_back(params->paths[i]);
- if (_find_owners(EditorFileSystem::get_singleton()->get_filesystem(), params->paths[i])) {
- used_in_resources = true;
+ Ref<Resource> resource = ResourceLoader::load(params->paths[i]);
+ if (resource.is_valid()) {
+ need_cleanup.push_back(params->paths[i]);
+ if (_find_owners(EditorFileSystem::get_singleton()->get_filesystem(), params->paths[i])) {
+ used_in_resources = true;
+ }
}
}
}
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 7b48e6fbe9..75c8ac11d0 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -112,6 +112,7 @@ void AnimationPlayerEditor::_notification(int p_what) {
}
last_active = player->is_playing();
+
updating = false;
} break;
@@ -942,11 +943,6 @@ void AnimationPlayerEditor::_update_player() {
onion_toggle->set_disabled(no_anims_found);
onion_skinning->set_disabled(no_anims_found);
- if (hack_disable_onion_skinning) {
- onion_toggle->set_disabled(true);
- onion_skinning->set_disabled(true);
- }
-
_update_animation_list_icons();
updating = false;
@@ -1150,33 +1146,33 @@ void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay)
float alpha_step = 1.0 / (onion.steps + 1);
- int cidx = 0;
+ uint32_t capture_idx = 0;
if (onion.past) {
- float alpha = 0;
+ float alpha = 0.0f;
do {
alpha += alpha_step;
- if (onion.captures_valid[cidx]) {
+ if (onion.captures_valid[capture_idx]) {
RS::get_singleton()->canvas_item_add_texture_rect_region(
- ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[cidx]), src_rect, Color(1, 1, 1, alpha));
+ ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[capture_idx]), src_rect, Color(1, 1, 1, alpha));
}
- cidx++;
- } while (cidx < onion.steps);
+ capture_idx++;
+ } while (capture_idx < onion.steps);
}
if (onion.future) {
- float alpha = 1;
- int base_cidx = cidx;
+ float alpha = 1.0f;
+ uint32_t base_cidx = capture_idx;
do {
alpha -= alpha_step;
- if (onion.captures_valid[cidx]) {
+ if (onion.captures_valid[capture_idx]) {
RS::get_singleton()->canvas_item_add_texture_rect_region(
- ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[cidx]), src_rect, Color(1, 1, 1, alpha));
+ ci, dst_rect, RS::get_singleton()->viewport_get_texture(onion.captures[capture_idx]), src_rect, Color(1, 1, 1, alpha));
}
- cidx++;
- } while (cidx < base_cidx + onion.steps); // In case there's the present capture at the end, skip it.
+ capture_idx++;
+ } while (capture_idx < base_cidx + onion.steps); // In case there's the present capture at the end, skip it.
}
}
@@ -1266,7 +1262,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool
if (!p_timeline_only) {
if (player->is_valid() && !p_set) {
- double delta = pos - player->get_current_animation_position();
+ double delta = player->get_current_animation_position();
player->seek(pos, true, true);
player->seek(pos + delta, true, true);
} else {
@@ -1394,7 +1390,10 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
onion.enabled = !onion.enabled;
if (onion.enabled) {
- _start_onion_skinning();
+ if (get_player() && !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
+ EditorNode::get_singleton()->show_warning(TTR("Onion skinning requires a RESET animation."));
+ }
+ _start_onion_skinning(); // It will check for RESET animation anyway.
} else {
_stop_onion_skinning();
}
@@ -1416,7 +1415,7 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) {
onion.steps = (p_option - ONION_SKINNING_1_STEP) + 1;
int one_frame_idx = menu->get_item_index(ONION_SKINNING_1_STEP);
for (int i = 0; i <= ONION_SKINNING_LAST_STEPS_OPTION - ONION_SKINNING_1_STEP; i++) {
- menu->set_item_checked(one_frame_idx + i, onion.steps == i + 1);
+ menu->set_item_checked(one_frame_idx + i, (int)onion.steps == i + 1);
}
} break;
case ONION_SKINNING_DIFFERENCES_ONLY: {
@@ -1475,15 +1474,15 @@ void AnimationPlayerEditor::_editor_visibility_changed() {
bool AnimationPlayerEditor::_are_onion_layers_valid() {
ERR_FAIL_COND_V(!onion.past && !onion.future, false);
- Point2 capture_size = get_tree()->get_root()->get_size();
- return onion.captures.size() == onion.get_needed_capture_count() && onion.capture_size == capture_size;
+ Size2 capture_size = DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID);
+ return onion.captures.size() == onion.get_capture_count() && onion.capture_size == capture_size;
}
void AnimationPlayerEditor::_allocate_onion_layers() {
_free_onion_layers();
- int captures = onion.get_needed_capture_count();
- Point2 capture_size = get_tree()->get_root()->get_size();
+ int captures = onion.get_capture_count();
+ Size2 capture_size = DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID);
onion.captures.resize(captures);
onion.captures_valid.resize(captures);
@@ -1492,7 +1491,7 @@ void AnimationPlayerEditor::_allocate_onion_layers() {
bool is_present = onion.differences_only && i == captures - 1;
// Each capture is a viewport with a canvas item attached that renders a full-size rect with the contents of the main viewport.
- onion.captures.write[i] = RS::get_singleton()->viewport_create();
+ onion.captures[i] = RS::get_singleton()->viewport_create();
RS::get_singleton()->viewport_set_size(onion.captures[i], capture_size.width, capture_size.height);
RS::get_singleton()->viewport_set_update_mode(onion.captures[i], RS::VIEWPORT_UPDATE_ALWAYS);
@@ -1502,13 +1501,13 @@ void AnimationPlayerEditor::_allocate_onion_layers() {
// Reset the capture canvas item to the current root viewport texture (defensive).
RS::get_singleton()->canvas_item_clear(onion.capture.canvas_item);
- RS::get_singleton()->canvas_item_add_texture_rect(onion.capture.canvas_item, Rect2(Point2(), capture_size), get_tree()->get_root()->get_texture()->get_rid());
+ RS::get_singleton()->canvas_item_add_texture_rect(onion.capture.canvas_item, Rect2(Point2(), Point2(capture_size.x, -capture_size.y)), get_tree()->get_root()->get_texture()->get_rid());
onion.capture_size = capture_size;
}
void AnimationPlayerEditor::_free_onion_layers() {
- for (int i = 0; i < onion.captures.size(); i++) {
+ for (uint32_t i = 0; i < onion.captures.size(); i++) {
if (onion.captures[i].is_valid()) {
RS::get_singleton()->free(onion.captures[i]);
}
@@ -1524,7 +1523,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() {
return;
}
- if (!onion.enabled || !is_processing() || !is_visible() || !get_player()) {
+ if (!onion.enabled || !is_visible() || !get_player() || !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
_stop_onion_skinning();
return;
}
@@ -1540,14 +1539,10 @@ void AnimationPlayerEditor::_prepare_onion_layers_1() {
}
// And go to next step afterwards.
- call_deferred(SNAME("_prepare_onion_layers_2"));
+ callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_prolog).call_deferred();
}
-void AnimationPlayerEditor::_prepare_onion_layers_1_deferred() {
- call_deferred(SNAME("_prepare_onion_layers_1"));
-}
-
-void AnimationPlayerEditor::_prepare_onion_layers_2() {
+void AnimationPlayerEditor::_prepare_onion_layers_2_prolog() {
Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
if (!anim.is_valid()) {
return;
@@ -1558,21 +1553,20 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
// Hide superfluous elements that would make the overlay unnecessary cluttered.
- Dictionary canvas_edit_state;
- Dictionary spatial_edit_state;
if (Node3DEditor::get_singleton()->is_visible()) {
// 3D
- spatial_edit_state = Node3DEditor::get_singleton()->get_state();
- Dictionary new_state = spatial_edit_state.duplicate();
+ onion.temp.spatial_edit_state = Node3DEditor::get_singleton()->get_state();
+ Dictionary new_state = onion.temp.spatial_edit_state.duplicate();
new_state["show_grid"] = false;
new_state["show_origin"] = false;
- Array orig_vp = spatial_edit_state["viewports"];
+ Array orig_vp = onion.temp.spatial_edit_state["viewports"];
Array vp;
vp.resize(4);
for (int i = 0; i < vp.size(); i++) {
Dictionary d = ((Dictionary)orig_vp[i]).duplicate();
d["use_environment"] = false;
d["doppler"] = false;
+ d["listener"] = false;
d["gizmos"] = onion.include_gizmos ? d["gizmos"] : Variant(false);
d["information"] = false;
vp[i] = d;
@@ -1580,23 +1574,27 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
new_state["viewports"] = vp;
// TODO: Save/restore only affected entries.
Node3DEditor::get_singleton()->set_state(new_state);
- } else { // CanvasItemEditor
- // 2D
- canvas_edit_state = CanvasItemEditor::get_singleton()->get_state();
- Dictionary new_state = canvas_edit_state.duplicate();
+ } else {
+ // CanvasItemEditor.
+ onion.temp.canvas_edit_state = CanvasItemEditor::get_singleton()->get_state();
+ Dictionary new_state = onion.temp.canvas_edit_state.duplicate();
+ new_state["show_origin"] = false;
new_state["show_grid"] = false;
new_state["show_rulers"] = false;
new_state["show_guides"] = false;
new_state["show_helpers"] = false;
new_state["show_zoom_control"] = false;
+ new_state["show_edit_locks"] = false;
+ new_state["grid_visibility"] = 2; // TODO: Expose CanvasItemEditor::GRID_VISIBILITY_HIDE somehow and use it.
+ new_state["show_transformation_gizmos"] = onion.include_gizmos ? new_state["gizmos"] : Variant(false);
// TODO: Save/restore only affected entries.
CanvasItemEditor::get_singleton()->set_state(new_state);
}
// Tweak the root viewport to ensure it's rendered before our target.
RID root_vp = get_tree()->get_root()->get_viewport_rid();
- Rect2 root_vp_screen_rect = Rect2(Vector2(), get_tree()->get_root()->get_size());
- RS::get_singleton()->viewport_attach_to_screen(root_vp, Rect2());
+ onion.temp.screen_rect = Rect2(Vector2(), DisplayServer::get_singleton()->window_get_size(DisplayServer::MAIN_WINDOW_ID));
+ RS::get_singleton()->viewport_attach_to_screen(root_vp, Rect2(), DisplayServer::INVALID_WINDOW_ID);
RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_ALWAYS);
RID present_rid;
@@ -1611,8 +1609,8 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
// Backup current animation state.
- Ref<AnimatedValuesBackup> backup_current = player->make_backup();
- float cpos = player->get_current_animation_position();
+ onion.temp.anim_values_backup = player->make_backup();
+ onion.temp.anim_player_position = player->get_current_animation_position();
// Render every past/future step with the capture shader.
@@ -1620,55 +1618,94 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
onion.capture.material->set_shader_parameter("bkg_color", GLOBAL_GET("rendering/environment/defaults/default_clear_color"));
onion.capture.material->set_shader_parameter("differences_only", onion.differences_only);
onion.capture.material->set_shader_parameter("present", onion.differences_only ? RS::get_singleton()->viewport_get_texture(present_rid) : RID());
-
- int step_off_a = onion.past ? -onion.steps : 0;
- int step_off_b = onion.future ? onion.steps : 0;
- int cidx = 0;
onion.capture.material->set_shader_parameter("dir_color", onion.force_white_modulate ? Color(1, 1, 1) : Color(EDITOR_GET("editors/animation/onion_layers_past_color")));
- for (int step_off = step_off_a; step_off <= step_off_b; step_off++) {
- if (step_off == 0) {
- // Skip present step and switch to the color of future.
- if (!onion.force_white_modulate) {
- onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color"));
- }
- continue;
- }
- float pos = cpos + step_off * anim->get_step();
+ uint32_t p_capture_idx = 0;
+ int first_step_offset = onion.past ? -(int)onion.steps : 0;
+ _prepare_onion_layers_2_step_prepare(first_step_offset, p_capture_idx);
+}
+
+void AnimationPlayerEditor::_prepare_onion_layers_2_step_prepare(int p_step_offset, uint32_t p_capture_idx) {
+ uint32_t next_capture_idx = p_capture_idx;
+ if (p_step_offset == 0) {
+ // Skip present step and switch to the color of future.
+ if (!onion.force_white_modulate) {
+ onion.capture.material->set_shader_parameter("dir_color", EDITOR_GET("editors/animation/onion_layers_future_color"));
+ }
+ } else {
+ Ref<Animation> anim = player->get_animation(player->get_assigned_animation());
+ double pos = onion.temp.anim_player_position + p_step_offset * anim->get_step();
bool valid = anim->get_loop_mode() != Animation::LOOP_NONE || (pos >= 0 && pos <= anim->get_length());
- onion.captures_valid.write[cidx] = valid;
+ onion.captures_valid[p_capture_idx] = valid;
if (valid) {
player->seek(pos, true);
- get_tree()->flush_transform_notifications(); // Needed for transforms of Node3Ds.
-
- RS::get_singleton()->viewport_set_active(onion.captures[cidx], true);
- RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[cidx]);
- RS::get_singleton()->draw(false);
- RS::get_singleton()->viewport_set_active(onion.captures[cidx], false);
+ OS::get_singleton()->get_main_loop()->process(0);
+ // This is the key: process the frame and let all callbacks/updates/notifications happen
+ // so everything (transforms, skeletons, etc.) is up-to-date visually.
+ callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_2_step_capture).bind(p_step_offset, p_capture_idx).call_deferred();
+ return;
+ } else {
+ next_capture_idx++;
}
+ }
+
+ int last_step_offset = onion.future ? onion.steps : 0;
+ if (p_step_offset < last_step_offset) {
+ _prepare_onion_layers_2_step_prepare(p_step_offset + 1, next_capture_idx);
+ } else {
+ _prepare_onion_layers_2_epilog();
+ }
+}
+
+void AnimationPlayerEditor::_prepare_onion_layers_2_step_capture(int p_step_offset, uint32_t p_capture_idx) {
+ DEV_ASSERT(p_step_offset != 0);
+ DEV_ASSERT(onion.captures_valid[p_capture_idx]);
- cidx++;
+ RID root_vp = get_tree()->get_root()->get_viewport_rid();
+ RS::get_singleton()->viewport_set_active(onion.captures[p_capture_idx], true);
+ RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[p_capture_idx]);
+ RS::get_singleton()->draw(false);
+ RS::get_singleton()->viewport_set_active(onion.captures[p_capture_idx], false);
+
+ int last_step_offset = onion.future ? onion.steps : 0;
+ if (p_step_offset < last_step_offset) {
+ _prepare_onion_layers_2_step_prepare(p_step_offset + 1, p_capture_idx + 1);
+ } else {
+ _prepare_onion_layers_2_epilog();
}
+}
+void AnimationPlayerEditor::_prepare_onion_layers_2_epilog() {
// Restore root viewport.
+ RID root_vp = get_tree()->get_root()->get_viewport_rid();
RS::get_singleton()->viewport_set_parent_viewport(root_vp, RID());
- RS::get_singleton()->viewport_attach_to_screen(root_vp, root_vp_screen_rect);
+ RS::get_singleton()->viewport_attach_to_screen(root_vp, onion.temp.screen_rect, DisplayServer::MAIN_WINDOW_ID);
RS::get_singleton()->viewport_set_update_mode(root_vp, RS::VIEWPORT_UPDATE_WHEN_VISIBLE);
- // Restore animation state
- // (Seeking with update=true wouldn't do the trick because the current value of the properties
- // may not match their value for the current point in the animation).
- player->seek(cpos, false);
- player->restore(backup_current);
+ // Restore animation state.
+ // Here we're combine the power of seeking back to the original position and
+ // restoring the values backup. In most cases they will bring the same value back,
+ // but there are cases handled by one that the other can't.
+ // Namely:
+ // - Seeking won't restore any values that may have been modified by the user
+ // in the node after the last time the AnimationPlayer updated it.
+ // - Restoring the backup won't account for values that are not directly involved
+ // in the animation but a consequence of them (e.g., SkeletonModification2DLookAt).
+ // FIXME: Since backup of values is based on the reset animation, only values
+ // backed by a proper reset animation will work correctly with onion
+ // skinning and the possibility to restore the values mentioned in the
+ // first point above is gone. Still good enough.
+ player->seek(onion.temp.anim_player_position, true, true);
+ player->restore(onion.temp.anim_values_backup);
// Restore state of main editors.
if (Node3DEditor::get_singleton()->is_visible()) {
// 3D
- Node3DEditor::get_singleton()->set_state(spatial_edit_state);
+ Node3DEditor::get_singleton()->set_state(onion.temp.spatial_edit_state);
} else { // CanvasItemEditor
// 2D
- CanvasItemEditor::get_singleton()->set_state(canvas_edit_state);
+ CanvasItemEditor::get_singleton()->set_state(onion.temp.canvas_edit_state);
}
// Update viewports with skin layers overlaid for the actual engine loop render.
@@ -1677,21 +1714,26 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
void AnimationPlayerEditor::_start_onion_skinning() {
- // FIXME: Using "process_frame" makes onion layers update one frame behind the current.
- if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
- get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
+ if (get_player() && !get_player()->has_animation(SceneStringNames::get_singleton()->RESET)) {
+ onion.enabled = false;
+ onion_toggle->set_pressed_no_signal(false);
+ return;
+ }
+ if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1))) {
+ get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1));
}
}
void AnimationPlayerEditor::_stop_onion_skinning() {
- if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
- get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
+ if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1))) {
+ get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1));
_free_onion_layers();
- // Clean up the overlay.
+ // Clean up.
onion.can_overlay = false;
plugin->update_overlays();
+ onion.temp = {};
}
}
@@ -1773,8 +1815,6 @@ void AnimationPlayerEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("_list_changed"), &AnimationPlayerEditor::_list_changed);
ClassDB::bind_method(D_METHOD("_animation_duplicate"), &AnimationPlayerEditor::_animation_duplicate);
- ClassDB::bind_method(D_METHOD("_prepare_onion_layers_1"), &AnimationPlayerEditor::_prepare_onion_layers_1);
- ClassDB::bind_method(D_METHOD("_prepare_onion_layers_2"), &AnimationPlayerEditor::_prepare_onion_layers_2);
ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning);
ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning);
@@ -1914,16 +1954,6 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
onion_skinning->get_popup()->add_check_item(TTR("Include Gizmos (3D)"), ONION_SKINNING_INCLUDE_GIZMOS);
hb->add_child(onion_skinning);
- // FIXME: Onion skinning disabled for now as it's broken and triggers fast
- // flickering red/blue modulation (GH-53870).
- if (hack_disable_onion_skinning) {
- onion_toggle->set_disabled(true);
- onion_toggle->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug."));
-
- onion_skinning->set_disabled(true);
- onion_skinning->set_tooltip_text(TTR("Onion Skinning temporarily disabled due to rendering bug."));
- }
-
hb->add_child(memnew(VSeparator));
pin = memnew(Button);
@@ -2013,24 +2043,13 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
track_editor->connect(SNAME("visibility_changed"), callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed));
- onion.enabled = false;
- onion.past = true;
- onion.future = false;
- onion.steps = 1;
- onion.differences_only = false;
- onion.force_white_modulate = false;
- onion.include_gizmos = false;
-
- onion.last_frame = 0;
- onion.can_overlay = false;
- onion.capture_size = Size2();
onion.capture.canvas = RS::get_singleton()->canvas_create();
onion.capture.canvas_item = RS::get_singleton()->canvas_item_create();
RS::get_singleton()->canvas_item_set_parent(onion.capture.canvas_item, onion.capture.canvas);
- onion.capture.material = Ref<ShaderMaterial>(memnew(ShaderMaterial));
+ onion.capture.material.instantiate();
- onion.capture.shader = Ref<Shader>(memnew(Shader));
+ onion.capture.shader.instantiate();
onion.capture.shader->set_code(R"(
// Animation editor onion skinning shader.
@@ -2047,10 +2066,15 @@ float zero_if_equal(vec4 a, vec4 b) {
void fragment() {
vec4 capture_samp = texture(TEXTURE, UV);
- vec4 present_samp = texture(present, UV);
float bkg_mask = zero_if_equal(capture_samp, bkg_color);
- float diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color);
- diff_mask = min(1.0, diff_mask + float(!differences_only));
+ float diff_mask = 1.0;
+ if (differences_only) {
+ // FIXME: If Y-flips across render target, canvas item, etc. was handled correctly,
+ // this would not be as convoluted in the shader.
+ vec4 capture_samp2 = texture(TEXTURE, vec2(UV.x, 1.0 - UV.y));
+ vec4 present_samp = texture(present, vec2(UV.x, 1.0 - UV.y));
+ diff_mask = 1.0 - zero_if_equal(present_samp, bkg_color);
+ }
COLOR = vec4(capture_samp.rgb * dir_color.rgb, bkg_mask * diff_mask);
}
)");
@@ -2061,6 +2085,7 @@ AnimationPlayerEditor::~AnimationPlayerEditor() {
_free_onion_layers();
RS::get_singleton()->free(onion.capture.canvas);
RS::get_singleton()->free(onion.capture.canvas_item);
+ onion.capture = {};
}
void AnimationPlayerEditorPlugin::_notification(int p_what) {
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 4763a008fe..6751933839 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -136,20 +136,18 @@ class AnimationPlayerEditor : public VBoxContainer {
AnimationTrackEditor *track_editor = nullptr;
static AnimationPlayerEditor *singleton;
- bool hack_disable_onion_skinning = true; // Temporary hack for GH-53870.
-
// Onion skinning.
struct {
// Settings.
bool enabled = false;
- bool past = false;
+ bool past = true;
bool future = false;
- int steps = 0;
+ uint32_t steps = 1;
bool differences_only = false;
bool force_white_modulate = false;
bool include_gizmos = false;
- int get_needed_capture_count() const {
+ uint32_t get_capture_count() const {
// 'Differences only' needs a capture of the present.
return (past && future ? 2 * steps : steps) + (differences_only ? 1 : 0);
}
@@ -158,14 +156,23 @@ class AnimationPlayerEditor : public VBoxContainer {
int64_t last_frame = 0;
int can_overlay = 0;
Size2 capture_size;
- Vector<RID> captures;
- Vector<bool> captures_valid;
+ LocalVector<RID> captures;
+ LocalVector<bool> captures_valid;
struct {
RID canvas;
RID canvas_item;
Ref<ShaderMaterial> material;
Ref<Shader> shader;
} capture;
+
+ // Cross-call state.
+ struct {
+ double anim_player_position = 0.0;
+ Ref<AnimatedValuesBackup> anim_values_backup;
+ Rect2 screen_rect;
+ Dictionary canvas_edit_state;
+ Dictionary spatial_edit_state;
+ } temp;
} onion;
void _select_anim_by_name(const String &p_anim);
@@ -215,8 +222,10 @@ class AnimationPlayerEditor : public VBoxContainer {
void _allocate_onion_layers();
void _free_onion_layers();
void _prepare_onion_layers_1();
- void _prepare_onion_layers_1_deferred();
- void _prepare_onion_layers_2();
+ void _prepare_onion_layers_2_prolog();
+ void _prepare_onion_layers_2_step_prepare(int p_step_offset, uint32_t p_capture_idx);
+ void _prepare_onion_layers_2_step_capture(int p_step_offset, uint32_t p_capture_idx);
+ void _prepare_onion_layers_2_epilog();
void _start_onion_skinning();
void _stop_onion_skinning();
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 6806deff67..4bfa6adaae 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -2276,6 +2276,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
NodePath root_path = get_tree()->get_edited_scene_root()->get_path();
StringName root_name = root_path.get_name(root_path.get_name_count() - 1);
+ int icon_max_width = EditorNode::get_singleton()->get_editor_theme()->get_constant(SNAME("class_icon_size"), EditorStringName(Editor));
for (int i = 0; i < selection_results.size(); i++) {
CanvasItem *item = selection_results[i].item;
@@ -2307,6 +2308,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) {
}
selection_menu->add_item((String)item->get_name() + suffix);
selection_menu->set_item_icon(i, icon);
+ selection_menu->set_item_icon_max_width(i, icon_max_width);
selection_menu->set_item_metadata(i, node_path);
selection_menu->set_item_tooltip(i, String(item->get_name()) + "\nType: " + item->get_class() + "\nPath: " + node_path);
}
@@ -3653,6 +3655,7 @@ void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Trans
void CanvasItemEditor::_draw_hover() {
List<Rect2> previous_rects;
+ Vector2 icon_size = Vector2(1, 1) * get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
for (int i = 0; i < hovering_results.size(); i++) {
Ref<Texture2D> node_icon = hovering_results[i].icon;
@@ -3661,9 +3664,9 @@ void CanvasItemEditor::_draw_hover() {
Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
Size2 node_name_size = font->get_string_size(node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
- Size2 item_size = Size2(node_icon->get_size().x + 4 + node_name_size.x, MAX(node_icon->get_size().y, node_name_size.y - 3));
+ Size2 item_size = Size2(icon_size.x + 4 + node_name_size.x, MAX(icon_size.y, node_name_size.y - 3));
- Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(node_icon->get_size().x, -node_icon->get_size().y) / 4);
+ Point2 pos = transform.xform(hovering_results[i].position) - Point2(0, item_size.y) + (Point2(icon_size.x, -icon_size.y) / 4);
// Rectify the position to avoid overlapping items
for (const Rect2 &E : previous_rects) {
if (E.intersects(Rect2(pos, item_size))) {
@@ -3674,10 +3677,10 @@ void CanvasItemEditor::_draw_hover() {
previous_rects.push_back(Rect2(pos, item_size));
// Draw icon
- viewport->draw_texture(node_icon, pos, Color(1.0, 1.0, 1.0, 0.5));
+ viewport->draw_texture_rect(node_icon, Rect2(pos, icon_size), false, Color(1.0, 1.0, 1.0, 0.5));
// Draw name
- viewport->draw_string(font, pos + Point2(node_icon->get_size().x + 4, item_size.y - 3), node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5));
+ viewport->draw_string(font, pos + Point2(icon_size.x + 4, item_size.y - 3), node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5));
}
}
@@ -5502,6 +5505,7 @@ CanvasItemEditor::CanvasItemEditor() {
selection_menu = memnew(PopupMenu);
add_child(selection_menu);
selection_menu->set_min_size(Vector2(100, 0));
+ selection_menu->set_auto_translate(false);
selection_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_selection_result_pressed));
selection_menu->connect("popup_hide", callable_mp(this, &CanvasItemEditor::_selection_menu_hide), CONNECT_DEFERRED);
diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp
index 4185460126..9d66e606b0 100644
--- a/editor/plugins/path_3d_editor_plugin.cpp
+++ b/editor/plugins/path_3d_editor_plugin.cpp
@@ -274,6 +274,7 @@ void Path3DGizmo::redraw() {
Ref<StandardMaterial3D> path_material = gizmo_plugin->get_material("path_material", this);
Ref<StandardMaterial3D> path_thin_material = gizmo_plugin->get_material("path_thin_material", this);
+ Ref<StandardMaterial3D> path_tilt_material = gizmo_plugin->get_material("path_tilt_material", this);
Ref<StandardMaterial3D> handles_material = gizmo_plugin->get_material("handles");
Ref<StandardMaterial3D> sec_handles_material = gizmo_plugin->get_material("sec_handles");
@@ -339,6 +340,7 @@ void Path3DGizmo::redraw() {
// 2. Draw handles when selected.
if (Path3DEditorPlugin::singleton->get_edited_path() == path) {
PackedVector3Array handle_lines;
+ PackedVector3Array tilt_handle_lines;
PackedVector3Array primary_handle_points;
PackedVector3Array secondary_handle_points;
PackedInt32Array collected_secondary_handle_ids; // Avoid shadowing member on Node3DEditorGizmo.
@@ -367,7 +369,7 @@ void Path3DGizmo::redraw() {
}
// Collect out-handles except for the last point.
- if (idx < c->get_point_count()) {
+ if (idx < c->get_point_count() - 1) {
info.type = HandleType::HANDLE_TYPE_OUT;
const int handle_idx = idx * 3 + 1;
collected_secondary_handle_ids.append(handle_idx);
@@ -389,9 +391,9 @@ void Path3DGizmo::redraw() {
const Basis posture = c->get_point_baked_posture(idx, true);
const Vector3 up = posture.get_column(1);
- secondary_handle_points.append(pos + up);
- handle_lines.append(pos);
- handle_lines.append(pos + up);
+ secondary_handle_points.append(pos + up * disk_size);
+ tilt_handle_lines.append(pos);
+ tilt_handle_lines.append(pos + up * disk_size);
}
// Tilt disk.
@@ -403,13 +405,13 @@ void Path3DGizmo::redraw() {
PackedVector3Array disk;
disk.append(pos);
- const int n = 24;
+ const int n = 36;
for (int i = 0; i <= n; i++) {
const float a = Math_TAU * i / n;
const Vector3 edge = sin(a) * side + cos(a) * up;
- disk.append(pos + edge);
+ disk.append(pos + edge * disk_size);
}
- add_vertices(disk, path_material, Mesh::PRIMITIVE_LINE_STRIP);
+ add_vertices(disk, path_tilt_material, Mesh::PRIMITIVE_LINE_STRIP);
}
}
}
@@ -417,6 +419,11 @@ void Path3DGizmo::redraw() {
if (handle_lines.size() > 1) {
add_lines(handle_lines, path_thin_material);
}
+
+ if (tilt_handle_lines.size() > 1) {
+ add_lines(tilt_handle_lines, path_tilt_material);
+ }
+
if (primary_handle_points.size()) {
add_handles(primary_handle_points, handles_material);
}
@@ -426,8 +433,9 @@ void Path3DGizmo::redraw() {
}
}
-Path3DGizmo::Path3DGizmo(Path3D *p_path) {
+Path3DGizmo::Path3DGizmo(Path3D *p_path, float p_disk_size) {
path = p_path;
+ disk_size = p_disk_size;
set_node_3d(p_path);
orig_in_length = 0;
orig_out_length = 0;
@@ -555,7 +563,7 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p
real_t dist_to_p = p_camera->unproject_position(gt.xform(c->get_point_position(i))).distance_to(mbpos);
real_t dist_to_p_out = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_out(i))).distance_to(mbpos);
real_t dist_to_p_in = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_in(i))).distance_to(mbpos);
- real_t dist_to_p_up = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_baked_posture(i, true).get_column(1))).distance_to(mbpos);
+ real_t dist_to_p_up = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_baked_posture(i, true).get_column(1) * disk_size)).distance_to(mbpos);
// Find the offset and point index of the place to break up.
// Also check for the control points.
@@ -721,8 +729,9 @@ Path3DEditorPlugin::Path3DEditorPlugin() {
mirror_handle_angle = true;
mirror_handle_length = true;
- Ref<Path3DGizmoPlugin> gizmo_plugin;
- gizmo_plugin.instantiate();
+ disk_size = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_settings/path3d_tilt_disk_size", 0.8);
+
+ Ref<Path3DGizmoPlugin> gizmo_plugin = memnew(Path3DGizmoPlugin(disk_size));
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
topmenu_bar = memnew(HBoxContainer);
@@ -789,7 +798,7 @@ Ref<EditorNode3DGizmo> Path3DGizmoPlugin::create_gizmo(Node3D *p_spatial) {
Path3D *path = Object::cast_to<Path3D>(p_spatial);
if (path) {
- ref = Ref<Path3DGizmo>(memnew(Path3DGizmo(path)));
+ ref = Ref<Path3DGizmo>(memnew(Path3DGizmo(path, disk_size)));
}
return ref;
@@ -803,10 +812,14 @@ int Path3DGizmoPlugin::get_priority() const {
return -1;
}
-Path3DGizmoPlugin::Path3DGizmoPlugin() {
- Color path_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/path", Color(0.5, 0.5, 1.0, 0.8));
+Path3DGizmoPlugin::Path3DGizmoPlugin(float p_disk_size) {
+ Color path_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/path", Color(0.5, 0.5, 1.0, 0.9));
+ Color path_tilt_color = EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/path_tilt", Color(1.0, 1.0, 0.4, 0.9));
+ disk_size = p_disk_size;
+
create_material("path_material", path_color);
- create_material("path_thin_material", Color(0.5, 0.5, 0.5));
+ create_material("path_thin_material", Color(0.6, 0.6, 0.6));
+ create_material("path_tilt_material", path_tilt_color);
create_handle_material("handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorPathSmoothHandle"), EditorStringName(EditorIcons)));
create_handle_material("sec_handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorCurveHandle"), EditorStringName(EditorIcons)));
}
diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h
index 871e6a1563..5a5f76b015 100644
--- a/editor/plugins/path_3d_editor_plugin.h
+++ b/editor/plugins/path_3d_editor_plugin.h
@@ -58,6 +58,7 @@ class Path3DGizmo : public EditorNode3DGizmo {
mutable Vector3 original;
mutable float orig_in_length;
mutable float orig_out_length;
+ mutable float disk_size = 0.8;
// Cache information of secondary handles.
Vector<HandleInfo> _secondary_handles_info;
@@ -69,19 +70,21 @@ public:
virtual void commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override;
virtual void redraw() override;
- Path3DGizmo(Path3D *p_path = nullptr);
+ Path3DGizmo(Path3D *p_path = nullptr, float p_disk_size = 0.8);
};
class Path3DGizmoPlugin : public EditorNode3DGizmoPlugin {
GDCLASS(Path3DGizmoPlugin, EditorNode3DGizmoPlugin);
+ float disk_size = 0.8;
+
protected:
Ref<EditorNode3DGizmo> create_gizmo(Node3D *p_spatial) override;
public:
String get_gizmo_name() const override;
int get_priority() const override;
- Path3DGizmoPlugin();
+ Path3DGizmoPlugin(float p_disk_size);
};
class Path3DEditorPlugin : public EditorPlugin {
@@ -95,6 +98,8 @@ class Path3DEditorPlugin : public EditorPlugin {
Button *curve_close = nullptr;
MenuButton *handle_menu = nullptr;
+ float disk_size = 0.8;
+
enum Mode {
MODE_CREATE,
MODE_EDIT,
diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp
index 5228db03b9..8c0a5b999a 100644
--- a/editor/property_selector.cpp
+++ b/editor/property_selector.cpp
@@ -370,46 +370,15 @@ void PropertySelector::_item_selected() {
class_type = instance->get_class();
}
- DocTools *dd = EditorHelp::get_doc_data();
String text;
- if (properties) {
- while (!class_type.is_empty()) {
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_type);
- if (E) {
- for (int i = 0; i < E->value.properties.size(); i++) {
- if (E->value.properties[i].name == name) {
- text = DTR(E->value.properties[i].description);
- break;
- }
- }
- }
-
- if (!text.is_empty()) {
- break;
- }
-
- // The property may be from a parent class, keep looking.
- class_type = ClassDB::get_parent_class(class_type);
+ while (!class_type.is_empty()) {
+ text = properties ? help_bit->get_property_description(class_type, name) : help_bit->get_method_description(class_type, name);
+ if (!text.is_empty()) {
+ break;
}
- } else {
- while (!class_type.is_empty()) {
- HashMap<String, DocData::ClassDoc>::Iterator E = dd->class_list.find(class_type);
- if (E) {
- for (int i = 0; i < E->value.methods.size(); i++) {
- if (E->value.methods[i].name == name) {
- text = DTR(E->value.methods[i].description);
- break;
- }
- }
- }
- if (!text.is_empty()) {
- break;
- }
-
- // The method may be from a parent class, keep looking.
- class_type = ClassDB::get_parent_class(class_type);
- }
+ // It may be from a parent class, keep looking.
+ class_type = ClassDB::get_parent_class(class_type);
}
if (!text.is_empty()) {
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index a83dedad6d..e7727aec4b 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -1683,6 +1683,32 @@ bool SceneTreeDock::_check_node_path_recursive(Node *p_root_node, Variant &r_var
}
} break;
+ case Variant::OBJECT: {
+ Resource *resource = Object::cast_to<Resource>(r_variant);
+ if (!resource) {
+ break;
+ }
+
+ List<PropertyInfo> properties;
+ resource->get_property_list(&properties);
+
+ for (const PropertyInfo &E : properties) {
+ if (!(E.usage & (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR))) {
+ continue;
+ }
+ String propertyname = E.name;
+ Variant old_variant = resource->get(propertyname);
+ Variant updated_variant = old_variant;
+ if (_check_node_path_recursive(p_root_node, updated_variant, p_renames)) {
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->add_do_property(resource, propertyname, updated_variant);
+ undo_redo->add_undo_property(resource, propertyname, old_variant);
+ resource->set(propertyname, updated_variant);
+ }
+ }
+ break;
+ };
+
default: {
}
}
diff --git a/main/main.cpp b/main/main.cpp
index f0a05fcd63..ef0de8d2bf 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1746,21 +1746,31 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
{
String driver_hints = "";
String driver_hints_angle = "";
+ String driver_hints_egl = "";
#ifdef GLES3_ENABLED
driver_hints = "opengl3";
driver_hints_angle = "opengl3,opengl3_angle";
+ driver_hints_egl = "opengl3,opengl3_es";
#endif
String default_driver = driver_hints.get_slice(",", 0);
+ String default_driver_macos = default_driver;
+#if defined(GLES3_ENABLED) && defined(EGL_STATIC) && defined(MACOS_ENABLED)
+ default_driver_macos = "opengl3_angle"; // Default to ANGLE if it's built-in.
+#endif
GLOBAL_DEF_RST("rendering/gl_compatibility/driver", default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.windows", PROPERTY_HINT_ENUM, driver_hints_angle), default_driver);
- GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.linuxbsd", PROPERTY_HINT_ENUM, driver_hints), default_driver);
+ GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.linuxbsd", PROPERTY_HINT_ENUM, driver_hints_egl), default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.web", PROPERTY_HINT_ENUM, driver_hints), default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.android", PROPERTY_HINT_ENUM, driver_hints), default_driver);
GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.ios", PROPERTY_HINT_ENUM, driver_hints), default_driver);
- GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.macos", PROPERTY_HINT_ENUM, driver_hints_angle), default_driver);
+ GLOBAL_DEF_RST(PropertyInfo(Variant::STRING, "rendering/gl_compatibility/driver.macos", PROPERTY_HINT_ENUM, driver_hints_angle), default_driver_macos);
+
GLOBAL_DEF_RST("rendering/gl_compatibility/nvidia_disable_threaded_optimization", true);
+ GLOBAL_DEF_RST("rendering/gl_compatibility/fallback_to_angle", true);
+
+ GLOBAL_DEF_RST(PropertyInfo(Variant::ARRAY, "rendering/gl_compatibility/force_angle_on_devices", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::DICTIONARY, PROPERTY_HINT_NONE, String())), Array());
}
// Start with RenderingDevice-based backends. Should be included if any RD driver present.
@@ -1834,7 +1844,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
// Set a default renderer if none selected. Try to choose one that matches the driver.
if (rendering_method.is_empty()) {
- if (rendering_driver == "opengl3" || rendering_driver == "opengl3_angle") {
+ if (rendering_driver == "opengl3" || rendering_driver == "opengl3_angle" || rendering_driver == "opengl3_es") {
rendering_method = "gl_compatibility";
} else {
rendering_method = "forward_plus";
@@ -1853,6 +1863,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (rendering_method == "gl_compatibility") {
available_drivers.push_back("opengl3");
available_drivers.push_back("opengl3_angle");
+ available_drivers.push_back("opengl3_es");
}
#endif
if (available_drivers.is_empty()) {
@@ -2113,6 +2124,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
GLOBAL_DEF_BASIC("xr/openxr/submit_depth_buffer", false);
GLOBAL_DEF_BASIC("xr/openxr/startup_alert", true);
+ // XR project extensions settings.
+ GLOBAL_DEF_BASIC("xr/openxr/extensions/eye_gaze_interaction", false);
+
#ifdef TOOLS_ENABLED
// Disabled for now, using XR inside of the editor we'll be working on during the coming months.
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 1be690d894..2bfa191ff1 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -442,7 +442,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
if (str[k] == '(') {
in_function_name = true;
- } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR)) {
+ } else if (prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::VAR) || prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FOR)) {
in_variable_declaration = true;
}
@@ -480,7 +480,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_function_args = false;
}
- if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[') {
+ if (expect_type && (prev_is_char || str[j] == '=') && str[j] != '[' && str[j] != '.') {
expect_type = false;
}
@@ -562,16 +562,11 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
} else if (in_keyword) {
next_type = KEYWORD;
color = keyword_color;
- } else if (in_member_variable) {
- next_type = MEMBER;
- color = member_color;
} else if (in_signal_declaration) {
next_type = SIGNAL;
-
color = member_color;
} else if (in_function_name) {
next_type = FUNCTION;
-
if (!in_lambda && prev_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) {
color = function_definition_color;
} else {
@@ -586,6 +581,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
} else if (expect_type) {
next_type = TYPE;
color = type_color;
+ } else if (in_member_variable) {
+ next_type = MEMBER;
+ color = member_color;
} else {
next_type = IDENTIFIER;
}
@@ -683,6 +681,12 @@ void GDScriptSyntaxHighlighter::_update_cache() {
for (const String &E : core_types) {
class_names[StringName(E)] = basetype_color;
}
+ class_names[SNAME("Variant")] = basetype_color;
+ class_names[SNAME("void")] = basetype_color;
+ // `get_core_type_words()` doesn't return primitive types.
+ class_names[SNAME("bool")] = basetype_color;
+ class_names[SNAME("int")] = basetype_color;
+ class_names[SNAME("float")] = basetype_color;
/* Reserved words. */
const Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color");
@@ -697,6 +701,10 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
}
+ // Highlight `set` and `get` as "keywords" with the function color to avoid conflicts with method calls.
+ reserved_keywords[SNAME("set")] = function_color;
+ reserved_keywords[SNAME("get")] = function_color;
+
/* Global functions. */
List<StringName> global_function_list;
GDScriptUtilityFunctions::get_function_list(&global_function_list);
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index b5c80d9e2d..f10ed0df29 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2368,62 +2368,60 @@ void GDScriptLanguage::frame() {
/* EDITOR FUNCTIONS */
void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
- // TODO: Add annotations here?
+ // Please keep alphabetical order within categories.
static const char *_reserved_words[] = {
- // operators
+ // Control flow.
+ "break",
+ "continue",
+ "elif",
+ "else",
+ "for",
+ "if",
+ "match",
+ "pass",
+ "return",
+ "when",
+ "while",
+ // Declarations.
+ "class",
+ "class_name",
+ "const",
+ "enum",
+ "extends",
+ "func",
+ "namespace", // Reserved for potential future use.
+ "signal",
+ "static",
+ "trait", // Reserved for potential future use.
+ "var",
+ // Other keywords.
+ "await",
+ "breakpoint",
+ "self",
+ "super",
+ "yield", // Reserved for potential future use.
+ // Operators.
"and",
+ "as",
"in",
+ "is",
"not",
"or",
- // types and values
+ // Special values (tokenizer treats them as literals, not as tokens).
"false",
- "float",
- "int",
- "bool",
"null",
- "PI",
- "TAU",
+ "true",
+ // Constants.
"INF",
"NAN",
- "self",
- "true",
- "void",
- // functions
- "as",
+ "PI",
+ "TAU",
+ // Functions (highlighter uses global function color instead).
"assert",
- "await",
- "breakpoint",
- "class",
- "class_name",
- "extends",
- "is",
- "func",
"preload",
- "signal",
- "super",
- // var
- "const",
- "enum",
- "static",
- "var",
- // control flow
- "break",
- "continue",
- "if",
- "elif",
- "else",
- "for",
- "pass",
- "return",
- "match",
- "while",
- "when",
- // These keywords are not implemented currently, but reserved for (potential) future use.
- // We highlight them as keywords to make errors easier to understand.
- "trait",
- "namespace",
- "yield",
- nullptr
+ // Types (highlighter uses type color instead).
+ "void",
+ nullptr,
};
const char **w = _reserved_words;
@@ -2432,22 +2430,16 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
p_words->push_back(*w);
w++;
}
-
- List<StringName> functions;
- GDScriptUtilityFunctions::get_function_list(&functions);
-
- for (const StringName &E : functions) {
- p_words->push_back(String(E));
- }
}
bool GDScriptLanguage::is_control_flow_keyword(String p_keyword) const {
+ // Please keep alphabetical order.
return p_keyword == "break" ||
p_keyword == "continue" ||
p_keyword == "elif" ||
p_keyword == "else" ||
- p_keyword == "if" ||
p_keyword == "for" ||
+ p_keyword == "if" ||
p_keyword == "match" ||
p_keyword == "pass" ||
p_keyword == "return" ||
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 882c246706..0d06597bc0 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -3022,9 +3022,23 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
} else {
#ifdef DEBUG_ENABLED
mark_node_unsafe(p_call);
- // We don't know what type was expected since constructors support overloads.
- // TODO: Improve this by checking for matching candidates?
- parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", function_name, "<unknown type>", "Variant");
+ // Constructors support overloads.
+ Vector<String> types;
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ if (i != builtin_type && Variant::can_convert_strict((Variant::Type)i, builtin_type)) {
+ types.push_back(Variant::get_type_name((Variant::Type)i));
+ }
+ }
+ String expected_types = function_name;
+ if (types.size() == 1) {
+ expected_types += "\" or \"" + types[0];
+ } else if (types.size() >= 2) {
+ for (int i = 0; i < types.size() - 1; i++) {
+ expected_types += "\", \"" + types[i];
+ }
+ expected_types += "\", or \"" + types[types.size() - 1];
+ }
+ parser->push_warning(p_call->arguments[0], GDScriptWarning::UNSAFE_CALL_ARGUMENT, "1", "constructor", function_name, expected_types, "Variant");
#endif
p_call->set_datatype(call_type);
return;
@@ -3069,9 +3083,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
#ifdef DEBUG_ENABLED
if (!(par_type.is_variant() && par_type.is_hard_type())) {
GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype();
- if (arg_type.is_variant() || !arg_type.is_hard_type() || !is_type_compatible(arg_type, par_type, true)) {
+ if (arg_type.is_variant() || !arg_type.is_hard_type()) {
mark_node_unsafe(p_call);
- parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), function_name, par_type.to_string(), arg_type.to_string_strict());
+ parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "constructor", function_name, par_type.to_string(), arg_type.to_string_strict());
}
}
#endif
@@ -5057,7 +5071,7 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
// Argument can be anything, so this is unsafe (unless the parameter is a hard variant).
if (!(par_type.is_hard_type() && par_type.is_variant())) {
mark_node_unsafe(p_call->arguments[i]);
- parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), p_call->function_name, par_type.to_string(), arg_type.to_string_strict());
+ parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict());
}
#endif
} else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) {
@@ -5069,7 +5083,7 @@ void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
} else {
// Supertypes are acceptable for dynamic compliance, but it's unsafe.
mark_node_unsafe(p_call);
- parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), p_call->function_name, par_type.to_string(), arg_type.to_string_strict());
+ parser->push_warning(p_call->arguments[i], GDScriptWarning::UNSAFE_CALL_ARGUMENT, itos(i + 1), "function", p_call->function_name, par_type.to_string(), arg_type.to_string_strict());
#endif
}
#ifdef DEBUG_ENABLED
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index adfe4a3290..2f26069281 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3396,6 +3396,12 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
}
}
+ if ("Variant" == p_symbol) {
+ r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS;
+ r_result.class_name = "Variant";
+ return OK;
+ }
+
if ("PI" == p_symbol || "TAU" == p_symbol || "INF" == p_symbol || "NAN" == p_symbol) {
r_result.type = ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT;
r_result.class_name = "@GDScript";
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index cabac07ef9..1fe9b0425c 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -107,8 +107,8 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(1);
return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]);
case UNSAFE_CALL_ARGUMENT:
- CHECK_SYMBOLS(4);
- return vformat(R"*(The argument %s of the function "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3]);
+ CHECK_SYMBOLS(5);
+ return vformat(R"*(The argument %s of the %s "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]);
case UNSAFE_VOID_RETURN:
CHECK_SYMBOLS(2);
return vformat(R"*(The method "%s()" returns "void" but it's trying to return a call to "%s()" that can't be ensured to also be "void".)*", symbols[0], symbols[1]);
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd
index 573060ae0f..c6d9b37485 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.gd
@@ -7,8 +7,12 @@ func int_func(x: int) -> void:
func float_func(x: float) -> void:
print(x)
+func node_func(x: Node) -> void:
+ print(x)
+
# We don't want to execute it because of errors, just analyze.
func no_exec_test():
+ var variant: Variant = null
var untyped_int = 42
var untyped_string = "abc"
var variant_int: Variant = 42
@@ -33,5 +37,18 @@ func no_exec_test():
float_func(variant_string)
float_func(typed_int) # No warning.
+ node_func(variant)
+ node_func(Object.new())
+ node_func(Node.new()) # No warning.
+ node_func(Node2D.new()) # No warning.
+
+ # GH-82529
+ print(Callable(self, "test")) # No warning.
+ print(Callable(variant, "test"))
+
+ print(Dictionary(variant))
+ print(Vector2(variant))
+ print(int(variant))
+
func test():
pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out
index b8fcb67158..3084515233 100644
--- a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_call_argument.out
@@ -1,33 +1,57 @@
GDTEST_OK
>> WARNING
->> Line: 24
+>> Line: 28
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
>> WARNING
->> Line: 25
+>> Line: 29
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
>> WARNING
->> Line: 26
+>> Line: 30
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
>> WARNING
->> Line: 27
+>> Line: 31
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "int_func()" requires the subtype "int" but the supertype "Variant" was provided.
>> WARNING
->> Line: 30
+>> Line: 34
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
>> WARNING
->> Line: 31
+>> Line: 35
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
>> WARNING
->> Line: 32
+>> Line: 36
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
>> WARNING
->> Line: 33
+>> Line: 37
>> UNSAFE_CALL_ARGUMENT
>> The argument 1 of the function "float_func()" requires the subtype "float" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 40
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "node_func()" requires the subtype "Node" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 41
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the function "node_func()" requires the subtype "Node" but the supertype "Object" was provided.
+>> WARNING
+>> Line: 47
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the constructor "Callable()" requires the subtype "Object" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 49
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the constructor "Dictionary()" requires the subtype "Dictionary" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 50
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the constructor "Vector2()" requires the subtype "Vector2" or "Vector2i" but the supertype "Variant" was provided.
+>> WARNING
+>> Line: 51
+>> UNSAFE_CALL_ARGUMENT
+>> The argument 1 of the constructor "int()" requires the subtype "int", "bool", or "float" but the supertype "Variant" was provided.
diff --git a/modules/gdscript/tests/scripts/runtime/features/stringify.gd b/modules/gdscript/tests/scripts/runtime/features/stringify.gd
index 1e66d8f34a..0dbb252b0e 100644
--- a/modules/gdscript/tests/scripts/runtime/features/stringify.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/stringify.gd
@@ -24,7 +24,6 @@ func test():
print(StringName("hello"))
print(NodePath("hello/world"))
var node := Node.new()
- @warning_ignore("unsafe_call_argument")
print(RID(node)) # TODO: Why is the constructor (or implicit cast) not documented?
print(node.get_name)
print(node.property_list_changed)
diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd
index fb20817117..781843b8e2 100644
--- a/modules/gdscript/tests/scripts/utils.notest.gd
+++ b/modules/gdscript/tests/scripts/utils.notest.gd
@@ -17,7 +17,7 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String:
TYPE_OBJECT:
if not str(property.class_name).is_empty():
return property.class_name
- return variant_get_type_name(property.type)
+ return type_string(property.type)
static func get_property_signature(property: Dictionary, is_static: bool = false) -> String:
@@ -66,88 +66,6 @@ static func get_method_signature(method: Dictionary, is_signal: bool = false) ->
return result
-static func variant_get_type_name(type: Variant.Type) -> String:
- match type:
- TYPE_NIL:
- return "Nil" # `Nil` in core, `null` in GDScript.
- TYPE_BOOL:
- return "bool"
- TYPE_INT:
- return "int"
- TYPE_FLOAT:
- return "float"
- TYPE_STRING:
- return "String"
- TYPE_VECTOR2:
- return "Vector2"
- TYPE_VECTOR2I:
- return "Vector2i"
- TYPE_RECT2:
- return "Rect2"
- TYPE_RECT2I:
- return "Rect2i"
- TYPE_VECTOR3:
- return "Vector3"
- TYPE_VECTOR3I:
- return "Vector3i"
- TYPE_TRANSFORM2D:
- return "Transform2D"
- TYPE_VECTOR4:
- return "Vector4"
- TYPE_VECTOR4I:
- return "Vector4i"
- TYPE_PLANE:
- return "Plane"
- TYPE_QUATERNION:
- return "Quaternion"
- TYPE_AABB:
- return "AABB"
- TYPE_BASIS:
- return "Basis"
- TYPE_TRANSFORM3D:
- return "Transform3D"
- TYPE_PROJECTION:
- return "Projection"
- TYPE_COLOR:
- return "Color"
- TYPE_STRING_NAME:
- return "StringName"
- TYPE_NODE_PATH:
- return "NodePath"
- TYPE_RID:
- return "RID"
- TYPE_OBJECT:
- return "Object"
- TYPE_CALLABLE:
- return "Callable"
- TYPE_SIGNAL:
- return "Signal"
- TYPE_DICTIONARY:
- return "Dictionary"
- TYPE_ARRAY:
- return "Array"
- TYPE_PACKED_BYTE_ARRAY:
- return "PackedByteArray"
- TYPE_PACKED_INT32_ARRAY:
- return "PackedInt32Array"
- TYPE_PACKED_INT64_ARRAY:
- return "PackedInt64Array"
- TYPE_PACKED_FLOAT32_ARRAY:
- return "PackedFloat32Array"
- TYPE_PACKED_FLOAT64_ARRAY:
- return "PackedFloat64Array"
- TYPE_PACKED_STRING_ARRAY:
- return "PackedStringArray"
- TYPE_PACKED_VECTOR2_ARRAY:
- return "PackedVector2Array"
- TYPE_PACKED_VECTOR3_ARRAY:
- return "PackedVector3Array"
- TYPE_PACKED_COLOR_ARRAY:
- return "PackedColorArray"
- push_error("Argument `type` is invalid. Use `TYPE_*` constants.")
- return "<invalid type>"
-
-
static func get_property_hint_name(hint: PropertyHint) -> String:
match hint:
PROPERTY_HINT_NONE:
diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp
index b86a8b3cb1..467bedc4b2 100644
--- a/modules/gdscript/tests/test_gdscript.cpp
+++ b/modules/gdscript/tests/test_gdscript.cpp
@@ -223,6 +223,16 @@ void test(TestType p_type) {
// Initialize the language for the test routine.
init_language(fa->get_path_absolute().get_base_dir());
+ // Load global classes.
+ TypedArray<Dictionary> script_classes = ProjectSettings::get_singleton()->get_global_class_list();
+ for (int i = 0; i < script_classes.size(); i++) {
+ Dictionary c = script_classes[i];
+ if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base")) {
+ continue;
+ }
+ ScriptServer::add_global_class(c["class"], c["base"], c["language"], c["path"]);
+ }
+
Vector<uint8_t> buf;
uint64_t flen = fa->get_length();
buf.resize(flen + 1);
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 9bb4fd153b..907511d140 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -206,17 +206,19 @@ namespace GodotTools.Build
if (!File.Exists(buildInfo.Project))
return true; // No project to build.
- using var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1);
-
- pr.Step("Building project", 0);
+ bool success;
+ using (var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1))
+ {
+ pr.Step("Building project", 0);
+ success = Build(buildInfo);
+ }
- if (!Build(buildInfo))
+ if (!success)
{
ShowBuildErrorDialog("Failed to build project");
- return false;
}
- return true;
+ return success;
}
private static bool CleanProjectBlocking(BuildInfo buildInfo)
@@ -224,32 +226,36 @@ namespace GodotTools.Build
if (!File.Exists(buildInfo.Project))
return true; // No project to clean.
- using var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1);
-
- pr.Step("Cleaning project", 0);
+ bool success;
+ using (var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1))
+ {
+ pr.Step("Cleaning project", 0);
+ success = Build(buildInfo);
+ }
- if (!Build(buildInfo))
+ if (!success)
{
ShowBuildErrorDialog("Failed to clean project");
- return false;
}
- return true;
+ return success;
}
private static bool PublishProjectBlocking(BuildInfo buildInfo)
{
- using var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1);
-
- pr.Step("Running dotnet publish", 0);
+ bool success;
+ using (var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1))
+ {
+ pr.Step("Running dotnet publish", 0);
+ success = Publish(buildInfo);
+ }
- if (!Publish(buildInfo))
+ if (!success)
{
ShowBuildErrorDialog("Failed to publish .NET project");
- return false;
}
- return true;
+ return success;
}
private static BuildInfo CreateBuildInfo(
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 48e654c286..a00c812c79 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -65,6 +65,7 @@ namespace GodotTools
private bool CreateProjectSolution()
{
+ string errorMessage = null;
using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2))
{
pr.Step("Generating C# project...".TTR());
@@ -96,22 +97,23 @@ namespace GodotTools
}
catch (IOException e)
{
- ShowErrorDialog("Failed to save solution. Exception message: ".TTR() + e.Message);
- return false;
+ errorMessage = "Failed to save solution. Exception message: ".TTR() + e.Message;
}
-
- pr.Step("Done".TTR());
-
- // Here, after all calls to progress_task_step
- CallDeferred(nameof(_ShowDotnetFeatures));
}
else
{
- ShowErrorDialog("Failed to create C# project.".TTR());
+ errorMessage = "Failed to create C# project.".TTR();
}
+ }
- return true;
+ if (!string.IsNullOrEmpty(errorMessage))
+ {
+ ShowErrorDialog(errorMessage);
+ return false;
}
+
+ _ShowDotnetFeatures();
+ return true;
}
private void _ShowDotnetFeatures()
@@ -161,14 +163,14 @@ namespace GodotTools
{
_errorDialog.Title = title;
_errorDialog.DialogText = message;
- _errorDialog.PopupCentered();
+ EditorInterface.Singleton.PopupDialogCentered(_errorDialog);
}
public void ShowConfirmCreateSlnDialog()
{
_confirmCreateSlnDialog.Title = "C# solution already exists. This will override the existing C# project file, any manual changes will be lost.".TTR();
_confirmCreateSlnDialog.DialogText = "Create C# solution".TTR();
- _confirmCreateSlnDialog.PopupCentered();
+ EditorInterface.Singleton.PopupDialogCentered(_confirmCreateSlnDialog);
}
private static string _vsCodePath = string.Empty;
@@ -483,11 +485,11 @@ namespace GodotTools
_editorSettings = EditorInterface.Singleton.GetEditorSettings();
_errorDialog = new AcceptDialog();
- editorBaseControl.AddChild(_errorDialog);
+ _errorDialog.SetUnparentWhenInvisible(true);
_confirmCreateSlnDialog = new ConfirmationDialog();
+ _confirmCreateSlnDialog.SetUnparentWhenInvisible(true);
_confirmCreateSlnDialog.Confirmed += () => CreateProjectSolution();
- editorBaseControl.AddChild(_confirmCreateSlnDialog);
MSBuildPanel = new MSBuildPanel();
MSBuildPanel.BuildStateChanged += BuildStateChanged;
diff --git a/modules/openxr/SCsub b/modules/openxr/SCsub
index c3a5d82fc4..73a3723ea4 100644
--- a/modules/openxr/SCsub
+++ b/modules/openxr/SCsub
@@ -106,6 +106,7 @@ if env["opengl3"] and env["platform"] != "macos":
env_openxr.add_source_files(module_obj, "extensions/openxr_palm_pose_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_composition_layer_depth_extension.cpp")
+env_openxr.add_source_files(module_obj, "extensions/openxr_eye_gaze_interaction.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_htc_controller_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_htc_vive_tracker_extension.cpp")
env_openxr.add_source_files(module_obj, "extensions/openxr_huawei_controller_extension.cpp")
diff --git a/modules/openxr/action_map/openxr_action_map.cpp b/modules/openxr/action_map/openxr_action_map.cpp
index 6d79e33de8..72866f1cf7 100644
--- a/modules/openxr/action_map/openxr_action_map.cpp
+++ b/modules/openxr/action_map/openxr_action_map.cpp
@@ -206,7 +206,8 @@ void OpenXRActionMap::create_default_action_sets() {
"/user/vive_tracker_htcx/role/waist,"
"/user/vive_tracker_htcx/role/chest,"
"/user/vive_tracker_htcx/role/camera,"
- "/user/vive_tracker_htcx/role/keyboard");
+ "/user/vive_tracker_htcx/role/keyboard,"
+ "/user/eyes_ext");
Ref<OpenXRAction> aim_pose = action_set->add_new_action("aim_pose", "Aim pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> grip_pose = action_set->add_new_action("grip_pose", "Grip pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right");
Ref<OpenXRAction> palm_pose = action_set->add_new_action("palm_pose", "Palm pose", OpenXRAction::OPENXR_ACTION_POSE, "/user/hand/left,/user/hand/right");
@@ -503,6 +504,11 @@ void OpenXRActionMap::create_default_action_sets() {
"/user/vive_tracker_htcx/role/camera/output/haptic,"
"/user/vive_tracker_htcx/role/keyboard/output/haptic");
add_interaction_profile(profile);
+
+ // Create our eye gaze interaction profile
+ profile = OpenXRInteractionProfile::new_profile("/interaction_profiles/ext/eye_gaze_interaction");
+ profile->add_new_binding(default_pose, "/user/eyes_ext/input/gaze_ext/pose");
+ add_interaction_profile(profile);
}
void OpenXRActionMap::create_editor_action_sets() {
diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml
index d0630626e6..c7c666dc2f 100644
--- a/modules/openxr/doc_classes/OpenXRInterface.xml
+++ b/modules/openxr/doc_classes/OpenXRInterface.xml
@@ -77,6 +77,13 @@
Returns [code]true[/code] if the given action set is active.
</description>
</method>
+ <method name="is_eye_gaze_interaction_supported">
+ <return type="bool" />
+ <description>
+ Returns the capabilities of the eye gaze interaction extension.
+ [b]Note:[/b] This only returns a valid value after OpenXR has been initialized.
+ </description>
+ </method>
<method name="is_foveation_supported" qualifiers="const">
<return type="bool" />
<description>
diff --git a/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp b/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp
new file mode 100644
index 0000000000..59bdec5c8e
--- /dev/null
+++ b/modules/openxr/extensions/openxr_eye_gaze_interaction.cpp
@@ -0,0 +1,98 @@
+/**************************************************************************/
+/* openxr_eye_gaze_interaction.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "openxr_eye_gaze_interaction.h"
+
+#include "core/os/os.h"
+
+#include "../action_map/openxr_interaction_profile_metadata.h"
+
+OpenXREyeGazeInteractionExtension *OpenXREyeGazeInteractionExtension::singleton = nullptr;
+
+OpenXREyeGazeInteractionExtension *OpenXREyeGazeInteractionExtension::get_singleton() {
+ ERR_FAIL_NULL_V(singleton, nullptr);
+ return singleton;
+}
+
+OpenXREyeGazeInteractionExtension::OpenXREyeGazeInteractionExtension() {
+ singleton = this;
+}
+
+OpenXREyeGazeInteractionExtension::~OpenXREyeGazeInteractionExtension() {
+ singleton = nullptr;
+}
+
+HashMap<String, bool *> OpenXREyeGazeInteractionExtension::get_requested_extensions() {
+ HashMap<String, bool *> request_extensions;
+
+ request_extensions[XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME] = &available;
+
+ return request_extensions;
+}
+
+void *OpenXREyeGazeInteractionExtension::set_system_properties_and_get_next_pointer(void *p_next_pointer) {
+ if (!available) {
+ return p_next_pointer;
+ }
+
+ properties.type = XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT;
+ properties.next = p_next_pointer;
+ properties.supportsEyeGazeInteraction = false;
+
+ return &properties;
+}
+
+bool OpenXREyeGazeInteractionExtension::is_available() {
+ return available;
+}
+
+bool OpenXREyeGazeInteractionExtension::supports_eye_gaze_interaction() {
+ // The extension being available only means that the OpenXR Runtime supports the extension.
+ // The `supportsEyeGazeInteraction` is set to true if the device also supports this.
+ // Thus both need to be true.
+ // In addition, on mobile runtimes, the proper permission needs to be granted.
+ if (available && properties.supportsEyeGazeInteraction) {
+ return !OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature("PERMISSION_XR_EXT_eye_gaze_interaction");
+ }
+
+ return false;
+}
+
+void OpenXREyeGazeInteractionExtension::on_register_metadata() {
+ OpenXRInteractionProfileMetadata *metadata = OpenXRInteractionProfileMetadata::get_singleton();
+ ERR_FAIL_NULL(metadata);
+
+ // Eyes top path
+ metadata->register_top_level_path("Eye gaze tracker", "/user/eyes_ext", XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME);
+
+ // Eye gaze interaction
+ metadata->register_interaction_profile("Eye gaze", "/interaction_profiles/ext/eye_gaze_interaction", XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME);
+ metadata->register_io_path("/interaction_profiles/ext/eye_gaze_interaction", "Gaze pose", "/user/eyes_ext", "/user/eyes_ext/input/gaze_ext/pose", "", OpenXRAction::OPENXR_ACTION_POSE);
+}
diff --git a/modules/openxr/extensions/openxr_eye_gaze_interaction.h b/modules/openxr/extensions/openxr_eye_gaze_interaction.h
new file mode 100644
index 0000000000..704940ad26
--- /dev/null
+++ b/modules/openxr/extensions/openxr_eye_gaze_interaction.h
@@ -0,0 +1,58 @@
+/**************************************************************************/
+/* openxr_eye_gaze_interaction.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef OPENXR_EYE_GAZE_INTERACTION_H
+#define OPENXR_EYE_GAZE_INTERACTION_H
+
+#include "openxr_extension_wrapper.h"
+
+class OpenXREyeGazeInteractionExtension : public OpenXRExtensionWrapper {
+public:
+ static OpenXREyeGazeInteractionExtension *get_singleton();
+
+ OpenXREyeGazeInteractionExtension();
+ ~OpenXREyeGazeInteractionExtension();
+
+ virtual HashMap<String, bool *> get_requested_extensions() override;
+ virtual void *set_system_properties_and_get_next_pointer(void *p_next_pointer) override;
+
+ bool is_available();
+ bool supports_eye_gaze_interaction();
+
+ virtual void on_register_metadata() override;
+
+private:
+ static OpenXREyeGazeInteractionExtension *singleton;
+
+ bool available = false;
+ XrSystemEyeGazeInteractionPropertiesEXT properties;
+};
+
+#endif // OPENXR_EYE_GAZE_INTERACTION_H
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index 7b1530677f..67a459adb8 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -36,6 +36,8 @@
#include "extensions/openxr_hand_tracking_extension.h"
+#include "extensions/openxr_eye_gaze_interaction.h"
+
void OpenXRInterface::_bind_methods() {
// lifecycle signals
ADD_SIGNAL(MethodInfo("session_begun"));
@@ -119,6 +121,8 @@ void OpenXRInterface::_bind_methods() {
BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_DISTAL);
BIND_ENUM_CONSTANT(HAND_JOINT_LITTLE_TIP);
BIND_ENUM_CONSTANT(HAND_JOINT_MAX);
+
+ ClassDB::bind_method(D_METHOD("is_eye_gaze_interaction_supported"), &OpenXRInterface::is_eye_gaze_interaction_supported);
}
StringName OpenXRInterface::get_name() const {
@@ -152,7 +156,9 @@ PackedStringArray OpenXRInterface::get_suggested_tracker_names() const {
"/user/vive_tracker_htcx/role/waist",
"/user/vive_tracker_htcx/role/chest",
"/user/vive_tracker_htcx/role/camera",
- "/user/vive_tracker_htcx/role/keyboard"
+ "/user/vive_tracker_htcx/role/keyboard",
+
+ "/user/eyes_ext",
};
return arr;
@@ -705,6 +711,21 @@ Array OpenXRInterface::get_available_display_refresh_rates() const {
}
}
+bool OpenXRInterface::is_eye_gaze_interaction_supported() {
+ if (openxr_api == nullptr) {
+ return false;
+ } else if (!openxr_api->is_initialized()) {
+ return false;
+ } else {
+ OpenXREyeGazeInteractionExtension *eye_gaze_ext = OpenXREyeGazeInteractionExtension::get_singleton();
+ if (eye_gaze_ext == nullptr) {
+ return false;
+ } else {
+ return eye_gaze_ext->supports_eye_gaze_interaction();
+ }
+ }
+}
+
bool OpenXRInterface::is_action_set_active(const String &p_action_set) const {
for (ActionSet *action_set : action_sets) {
if (action_set->action_set_name == p_action_set) {
diff --git a/modules/openxr/openxr_interface.h b/modules/openxr/openxr_interface.h
index 38cf4bdbe8..a2cc2b27ff 100644
--- a/modules/openxr/openxr_interface.h
+++ b/modules/openxr/openxr_interface.h
@@ -107,6 +107,8 @@ public:
virtual PackedStringArray get_suggested_tracker_names() const override;
virtual TrackingStatus get_tracking_status() const override;
+ bool is_eye_gaze_interaction_supported();
+
bool initialize_on_startup() const;
virtual bool is_initialized() const override;
virtual bool initialize() override;
diff --git a/modules/openxr/register_types.cpp b/modules/openxr/register_types.cpp
index d69c803502..09a064b9a9 100644
--- a/modules/openxr/register_types.cpp
+++ b/modules/openxr/register_types.cpp
@@ -42,6 +42,7 @@
#include "scene/openxr_hand.h"
#include "extensions/openxr_composition_layer_depth_extension.h"
+#include "extensions/openxr_eye_gaze_interaction.h"
#include "extensions/openxr_fb_display_refresh_rate_extension.h"
#include "extensions/openxr_fb_passthrough_extension_wrapper.h"
#include "extensions/openxr_hand_tracking_extension.h"
@@ -104,6 +105,9 @@ void initialize_openxr_module(ModuleInitializationLevel p_level) {
#endif
// register our other extensions
+ if (GLOBAL_GET("xr/openxr/extensions/eye_gaze_interaction") && (!OS::get_singleton()->has_feature("mobile") || OS::get_singleton()->has_feature(XR_EXT_EYE_GAZE_INTERACTION_EXTENSION_NAME))) {
+ OpenXRAPI::register_extension_wrapper(memnew(OpenXREyeGazeInteractionExtension));
+ }
OpenXRAPI::register_extension_wrapper(memnew(OpenXRPalmPoseExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRPicoControllerExtension));
OpenXRAPI::register_extension_wrapper(memnew(OpenXRCompositionLayerDepthExtension));
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 9adb10236e..b605b29f84 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -397,6 +397,14 @@ void TextServerAdvanced::_free_rid(const RID &p_rid) {
font_owner.free(p_rid);
}
memdelete(fd);
+ } else if (font_var_owner.owns(p_rid)) {
+ MutexLock ftlock(ft_mutex);
+
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_rid);
+ {
+ font_var_owner.free(p_rid);
+ }
+ memdelete(fdv);
} else if (shaped_owner.owns(p_rid)) {
ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_rid);
{
@@ -409,7 +417,7 @@ void TextServerAdvanced::_free_rid(const RID &p_rid) {
bool TextServerAdvanced::_has(const RID &p_rid) {
_THREAD_SAFE_METHOD_
- return font_owner.owns(p_rid) || shaped_owner.owns(p_rid);
+ return font_owner.owns(p_rid) || font_var_owner.owns(p_rid) || shaped_owner.owns(p_rid);
}
bool TextServerAdvanced::_load_support_data(const String &p_filename) {
@@ -1809,7 +1817,7 @@ _FORCE_INLINE_ void TextServerAdvanced::_font_clear_cache(FontAdvanced *p_font_d
}
hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, nullptr);
MutexLock lock(fd->mutex);
@@ -1828,8 +1836,24 @@ RID TextServerAdvanced::_create_font() {
return font_owner.make_rid(fd);
}
+RID TextServerAdvanced::_create_font_linked_variation(const RID &p_font_rid) {
+ _THREAD_SAFE_METHOD_
+
+ RID rid = p_font_rid;
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(rid);
+ if (unlikely(fdv)) {
+ rid = fdv->base_font;
+ }
+ ERR_FAIL_COND_V(!font_owner.owns(rid), RID());
+
+ FontAdvancedLinkedVariation *new_fdv = memnew(FontAdvancedLinkedVariation);
+ new_fdv->base_font = rid;
+
+ return font_var_owner.make_rid(new_fdv);
+}
+
void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1840,7 +1864,7 @@ void TextServerAdvanced::_font_set_data(const RID &p_font_rid, const PackedByteA
}
void TextServerAdvanced::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1854,7 +1878,7 @@ void TextServerAdvanced::_font_set_face_index(const RID &p_font_rid, int64_t p_f
ERR_FAIL_COND(p_face_index < 0);
ERR_FAIL_COND(p_face_index >= 0x7FFF);
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1865,7 +1889,7 @@ void TextServerAdvanced::_font_set_face_index(const RID &p_font_rid, int64_t p_f
}
int64_t TextServerAdvanced::_font_get_face_index(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1873,7 +1897,7 @@ int64_t TextServerAdvanced::_font_get_face_index(const RID &p_font_rid) const {
}
int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1919,7 +1943,7 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1929,7 +1953,7 @@ void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontSty
}
BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1939,7 +1963,7 @@ BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p
}
void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const String &p_name) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1949,7 +1973,7 @@ void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const Strin
}
String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -1959,7 +1983,7 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weight) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1969,7 +1993,7 @@ void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
}
int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 400);
MutexLock lock(fd->mutex);
@@ -1979,7 +2003,7 @@ int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1989,7 +2013,7 @@ void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
}
int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 100);
MutexLock lock(fd->mutex);
@@ -1999,7 +2023,7 @@ int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_name) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2009,7 +2033,7 @@ void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_n
}
String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -2019,7 +2043,7 @@ String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const {
}
Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -2132,7 +2156,7 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid)
}
void TextServerAdvanced::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2143,7 +2167,7 @@ void TextServerAdvanced::_font_set_antialiasing(const RID &p_font_rid, TextServe
}
TextServer::FontAntialiasing TextServerAdvanced::_font_get_antialiasing(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TextServer::FONT_ANTIALIASING_NONE);
MutexLock lock(fd->mutex);
@@ -2151,7 +2175,7 @@ TextServer::FontAntialiasing TextServerAdvanced::_font_get_antialiasing(const RI
}
void TextServerAdvanced::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2167,7 +2191,7 @@ void TextServerAdvanced::_font_set_generate_mipmaps(const RID &p_font_rid, bool
}
bool TextServerAdvanced::_font_get_generate_mipmaps(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2175,7 +2199,7 @@ bool TextServerAdvanced::_font_get_generate_mipmaps(const RID &p_font_rid) const
}
void TextServerAdvanced::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2186,7 +2210,7 @@ void TextServerAdvanced::_font_set_multichannel_signed_distance_field(const RID
}
bool TextServerAdvanced::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2194,7 +2218,7 @@ bool TextServerAdvanced::_font_is_multichannel_signed_distance_field(const RID &
}
void TextServerAdvanced::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2205,7 +2229,7 @@ void TextServerAdvanced::_font_set_msdf_pixel_range(const RID &p_font_rid, int64
}
int64_t TextServerAdvanced::_font_get_msdf_pixel_range(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2213,7 +2237,7 @@ int64_t TextServerAdvanced::_font_get_msdf_pixel_range(const RID &p_font_rid) co
}
void TextServerAdvanced::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2224,7 +2248,7 @@ void TextServerAdvanced::_font_set_msdf_size(const RID &p_font_rid, int64_t p_ms
}
int64_t TextServerAdvanced::_font_get_msdf_size(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2232,7 +2256,7 @@ int64_t TextServerAdvanced::_font_get_msdf_size(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2240,7 +2264,7 @@ void TextServerAdvanced::_font_set_fixed_size(const RID &p_font_rid, int64_t p_f
}
int64_t TextServerAdvanced::_font_get_fixed_size(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2248,7 +2272,7 @@ int64_t TextServerAdvanced::_font_get_fixed_size(const RID &p_font_rid) const {
}
void TextServerAdvanced::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2256,7 +2280,7 @@ void TextServerAdvanced::_font_set_allow_system_fallback(const RID &p_font_rid,
}
bool TextServerAdvanced::_font_is_allow_system_fallback(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2264,7 +2288,7 @@ bool TextServerAdvanced::_font_is_allow_system_fallback(const RID &p_font_rid) c
}
void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2275,7 +2299,7 @@ void TextServerAdvanced::_font_set_force_autohinter(const RID &p_font_rid, bool
}
bool TextServerAdvanced::_font_is_force_autohinter(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2283,7 +2307,7 @@ bool TextServerAdvanced::_font_is_force_autohinter(const RID &p_font_rid) const
}
void TextServerAdvanced::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2294,7 +2318,7 @@ void TextServerAdvanced::_font_set_hinting(const RID &p_font_rid, TextServer::Hi
}
TextServer::Hinting TextServerAdvanced::_font_get_hinting(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, HINTING_NONE);
MutexLock lock(fd->mutex);
@@ -2302,7 +2326,7 @@ TextServer::Hinting TextServerAdvanced::_font_get_hinting(const RID &p_font_rid)
}
void TextServerAdvanced::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2310,7 +2334,7 @@ void TextServerAdvanced::_font_set_subpixel_positioning(const RID &p_font_rid, T
}
TextServer::SubpixelPositioning TextServerAdvanced::_font_get_subpixel_positioning(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, SUBPIXEL_POSITIONING_DISABLED);
MutexLock lock(fd->mutex);
@@ -2318,7 +2342,7 @@ TextServer::SubpixelPositioning TextServerAdvanced::_font_get_subpixel_positioni
}
void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_strength) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2329,7 +2353,7 @@ void TextServerAdvanced::_font_set_embolden(const RID &p_font_rid, double p_stre
}
double TextServerAdvanced::_font_get_embolden(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2338,29 +2362,38 @@ double TextServerAdvanced::_font_get_embolden(const RID &p_font_rid) const {
void TextServerAdvanced::_font_set_spacing(const RID &p_font_rid, SpacingType p_spacing, int64_t p_value) {
ERR_FAIL_INDEX((int)p_spacing, 4);
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_NULL(fd);
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ if (fdv->extra_spacing[p_spacing] != p_value) {
+ fdv->extra_spacing[p_spacing] = p_value;
+ }
+ } else {
+ FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL(fd);
- MutexLock lock(fd->mutex);
- if (fd->extra_spacing[p_spacing] != p_value) {
- _font_clear_cache(fd);
- fd->extra_spacing[p_spacing] = p_value;
+ MutexLock lock(fd->mutex);
+ if (fd->extra_spacing[p_spacing] != p_value) {
+ fd->extra_spacing[p_spacing] = p_value;
+ }
}
}
int64_t TextServerAdvanced::_font_get_spacing(const RID &p_font_rid, SpacingType p_spacing) const {
ERR_FAIL_INDEX_V((int)p_spacing, 4, 0);
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ return fdv->extra_spacing[p_spacing];
+ } else {
+ FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL_V(fd, 0);
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_NULL_V(fd, 0);
-
- MutexLock lock(fd->mutex);
-
- return fd->extra_spacing[p_spacing];
+ MutexLock lock(fd->mutex);
+ return fd->extra_spacing[p_spacing];
+ }
}
void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2371,7 +2404,7 @@ void TextServerAdvanced::_font_set_transform(const RID &p_font_rid, const Transf
}
Transform2D TextServerAdvanced::_font_get_transform(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Transform2D());
MutexLock lock(fd->mutex);
@@ -2379,7 +2412,7 @@ Transform2D TextServerAdvanced::_font_get_transform(const RID &p_font_rid) const
}
void TextServerAdvanced::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2390,7 +2423,7 @@ void TextServerAdvanced::_font_set_variation_coordinates(const RID &p_font_rid,
}
Dictionary TextServerAdvanced::_font_get_variation_coordinates(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -2398,7 +2431,7 @@ Dictionary TextServerAdvanced::_font_get_variation_coordinates(const RID &p_font
}
void TextServerAdvanced::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2409,7 +2442,7 @@ void TextServerAdvanced::_font_set_oversampling(const RID &p_font_rid, double p_
}
double TextServerAdvanced::_font_get_oversampling(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2417,7 +2450,7 @@ double TextServerAdvanced::_font_get_oversampling(const RID &p_font_rid) const {
}
TypedArray<Vector2i> TextServerAdvanced::_font_get_size_cache_list(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
@@ -2429,7 +2462,7 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_size_cache_list(const RID &p_
}
void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2441,7 +2474,7 @@ void TextServerAdvanced::_font_clear_size_cache(const RID &p_font_rid) {
}
void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2453,7 +2486,7 @@ void TextServerAdvanced::_font_remove_size_cache(const RID &p_font_rid, const Ve
}
void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2464,7 +2497,7 @@ void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
}
double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2480,7 +2513,7 @@ double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
}
void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
Vector2i size = _get_size(fd, p_size);
@@ -2490,7 +2523,7 @@ void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size
}
double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2506,7 +2539,7 @@ double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_si
}
void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2517,7 +2550,7 @@ void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int
}
double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2533,7 +2566,7 @@ double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, i
}
void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2544,7 +2577,7 @@ void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, in
}
double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2560,7 +2593,7 @@ double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid,
}
void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2576,7 +2609,7 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size,
}
double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2592,7 +2625,7 @@ double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size
}
int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -2604,7 +2637,7 @@ int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const
}
void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -2614,7 +2647,7 @@ void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vecto
}
void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2626,7 +2659,7 @@ void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vecto
}
void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
ERR_FAIL_COND(p_image.is_null());
@@ -2655,7 +2688,7 @@ void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Ve
}
Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Ref<Image>());
MutexLock lock(fd->mutex);
@@ -2669,7 +2702,7 @@ Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, co
void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) {
ERR_FAIL_COND(p_offsets.size() % 4 != 0);
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2688,7 +2721,7 @@ void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const
}
PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
@@ -2713,7 +2746,7 @@ PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font
}
PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
@@ -2729,7 +2762,7 @@ PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid,
}
void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2740,7 +2773,7 @@ void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2
}
void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2751,7 +2784,7 @@ void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2
}
double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) const {
- const FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ const FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -2765,7 +2798,7 @@ double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) c
}
Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -2803,7 +2836,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64
}
void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2818,7 +2851,7 @@ void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t
}
Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -2848,7 +2881,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const
}
void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2863,7 +2896,7 @@ void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vec
}
Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -2893,7 +2926,7 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve
}
void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2908,7 +2941,7 @@ void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vecto
}
Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Rect2());
MutexLock lock(fd->mutex);
@@ -2933,7 +2966,7 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
}
void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2948,7 +2981,7 @@ void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
}
int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, -1);
MutexLock lock(fd->mutex);
@@ -2973,7 +3006,7 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c
}
void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2988,7 +3021,7 @@ void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
}
RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, RID());
MutexLock lock(fd->mutex);
@@ -3034,7 +3067,7 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const
}
Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Size2());
MutexLock lock(fd->mutex);
@@ -3080,7 +3113,7 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co
}
Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -3130,7 +3163,7 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i
}
TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
@@ -3146,7 +3179,7 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon
}
void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3157,7 +3190,7 @@ void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t
}
void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3168,7 +3201,7 @@ void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
}
void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3179,7 +3212,7 @@ void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size
}
Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -3212,7 +3245,7 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s
}
int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_char, int64_t p_variation_selector) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + ".");
ERR_FAIL_COND_V_MSG((p_variation_selector >= 0xd800 && p_variation_selector <= 0xdfff) || (p_variation_selector > 0x10ffff), 0, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_variation_selector, 16) + ".");
@@ -3237,7 +3270,7 @@ int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t
}
int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_rid, int64_t p_size, int64_t p_glyph_index) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -3268,7 +3301,7 @@ int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_ri
}
bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), false, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + ".");
if (!fd) {
return false;
@@ -3289,7 +3322,7 @@ bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) c
}
String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -3322,7 +3355,7 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons
}
void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + ".");
ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + ".");
@@ -3357,7 +3390,7 @@ void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2
}
void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3388,7 +3421,7 @@ void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2
}
void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3480,7 +3513,7 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3572,7 +3605,7 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
bool TextServerAdvanced::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -3584,7 +3617,7 @@ bool TextServerAdvanced::_font_is_language_supported(const RID &p_font_rid, cons
}
void TextServerAdvanced::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3592,7 +3625,7 @@ void TextServerAdvanced::_font_set_language_support_override(const RID &p_font_r
}
bool TextServerAdvanced::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -3600,7 +3633,7 @@ bool TextServerAdvanced::_font_get_language_support_override(const RID &p_font_r
}
void TextServerAdvanced::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3608,7 +3641,7 @@ void TextServerAdvanced::_font_remove_language_support_override(const RID &p_fon
}
PackedStringArray TextServerAdvanced::_font_get_language_support_overrides(const RID &p_font_rid) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
@@ -3620,7 +3653,7 @@ PackedStringArray TextServerAdvanced::_font_get_language_support_overrides(const
}
bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -3634,7 +3667,7 @@ bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const
}
void TextServerAdvanced::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3642,7 +3675,7 @@ void TextServerAdvanced::_font_set_script_support_override(const RID &p_font_rid
}
bool TextServerAdvanced::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -3650,7 +3683,7 @@ bool TextServerAdvanced::_font_get_script_support_override(const RID &p_font_rid
}
void TextServerAdvanced::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3658,7 +3691,7 @@ void TextServerAdvanced::_font_remove_script_support_override(const RID &p_font_
}
PackedStringArray TextServerAdvanced::_font_get_script_support_overrides(const RID &p_font_rid) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
@@ -3670,7 +3703,7 @@ PackedStringArray TextServerAdvanced::_font_get_script_support_overrides(const R
}
void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -3680,7 +3713,7 @@ void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_
}
Dictionary TextServerAdvanced::_font_get_opentype_feature_overrides(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -3688,7 +3721,7 @@ Dictionary TextServerAdvanced::_font_get_opentype_feature_overrides(const RID &p
}
Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -3698,7 +3731,7 @@ Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_ri
}
Dictionary TextServerAdvanced::_font_supported_variation_list(const RID &p_font_rid) const {
- FontAdvanced *fd = font_owner.get_or_null(p_font_rid);
+ FontAdvanced *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -4051,7 +4084,7 @@ bool TextServerAdvanced::_shaped_text_add_string(const RID &p_shaped, const Stri
MutexLock lock(sd->mutex);
for (int i = 0; i < p_fonts.size(); i++) {
- ERR_FAIL_COND_V(!font_owner.get_or_null(p_fonts[i]), false);
+ ERR_FAIL_COND_V(!_get_font_data(p_fonts[i]), false);
}
if (p_text.is_empty()) {
@@ -5633,7 +5666,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
return;
}
- FontAdvanced *fd = font_owner.get_or_null(f);
+ FontAdvanced *fd = _get_font_data(f);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index 7445becfae..57cca819be 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -293,6 +293,11 @@ class TextServerAdvanced : public TextServerExtension {
}
};
+ struct FontAdvancedLinkedVariation {
+ RID base_font;
+ int extra_spacing[4] = { 0, 0, 0, 0 };
+ };
+
struct FontAdvanced {
Mutex mutex;
@@ -534,9 +539,19 @@ class TextServerAdvanced : public TextServerExtension {
// Common data.
double oversampling = 1.0;
+ mutable RID_PtrOwner<FontAdvancedLinkedVariation> font_var_owner;
mutable RID_PtrOwner<FontAdvanced> font_owner;
mutable RID_PtrOwner<ShapedTextDataAdvanced> shaped_owner;
+ _FORCE_INLINE_ FontAdvanced *_get_font_data(const RID &p_font_rid) const {
+ RID rid = p_font_rid;
+ FontAdvancedLinkedVariation *fdv = font_var_owner.get_or_null(rid);
+ if (unlikely(fdv)) {
+ rid = fdv->base_font;
+ }
+ return font_owner.get_or_null(rid);
+ }
+
struct SystemFontKey {
String font_name;
TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY;
@@ -704,6 +719,7 @@ public:
/* Font interface */
MODBIND0R(RID, create_font);
+ MODBIND1R(RID, create_font_linked_variation, const RID &);
MODBIND2(font_set_data, const RID &, const PackedByteArray &);
MODBIND3(font_set_data_ptr, const RID &, const uint8_t *, int64_t);
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index c3b64929a9..0cfbf7f530 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -123,6 +123,14 @@ void TextServerFallback::_free_rid(const RID &p_rid) {
font_owner.free(p_rid);
}
memdelete(fd);
+ } else if (font_var_owner.owns(p_rid)) {
+ MutexLock ftlock(ft_mutex);
+
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_rid);
+ {
+ font_var_owner.free(p_rid);
+ }
+ memdelete(fdv);
} else if (shaped_owner.owns(p_rid)) {
ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_rid);
{
@@ -935,8 +943,24 @@ RID TextServerFallback::_create_font() {
return font_owner.make_rid(fd);
}
+RID TextServerFallback::_create_font_linked_variation(const RID &p_font_rid) {
+ _THREAD_SAFE_METHOD_
+
+ RID rid = p_font_rid;
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(rid);
+ if (unlikely(fdv)) {
+ rid = fdv->base_font;
+ }
+ ERR_FAIL_COND_V(!font_owner.owns(rid), RID());
+
+ FontFallbackLinkedVariation *new_fdv = memnew(FontFallbackLinkedVariation);
+ new_fdv->base_font = rid;
+
+ return font_var_owner.make_rid(new_fdv);
+}
+
void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -947,7 +971,7 @@ void TextServerFallback::_font_set_data(const RID &p_font_rid, const PackedByteA
}
void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -958,7 +982,7 @@ void TextServerFallback::_font_set_data_ptr(const RID &p_font_rid, const uint8_t
}
void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontStyle> p_style) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -971,7 +995,7 @@ void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_f
ERR_FAIL_COND(p_face_index < 0);
ERR_FAIL_COND(p_face_index >= 0x7FFF);
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -982,7 +1006,7 @@ void TextServerFallback::_font_set_face_index(const RID &p_font_rid, int64_t p_f
}
int64_t TextServerFallback::_font_get_face_index(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -990,7 +1014,7 @@ int64_t TextServerFallback::_font_get_face_index(const RID &p_font_rid) const {
}
int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1036,7 +1060,7 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const {
}
BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1046,7 +1070,7 @@ BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p
}
void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const String &p_name) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1056,7 +1080,7 @@ void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const Strin
}
String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -1066,7 +1090,7 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weight) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1076,7 +1100,7 @@ void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weigh
}
int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 400);
MutexLock lock(fd->mutex);
@@ -1086,7 +1110,7 @@ int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stretch) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1096,7 +1120,7 @@ void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stre
}
int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 100);
MutexLock lock(fd->mutex);
@@ -1106,7 +1130,7 @@ int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_name) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1116,7 +1140,7 @@ void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_n
}
String TextServerFallback::_font_get_name(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -1126,7 +1150,7 @@ String TextServerFallback::_font_get_name(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServer::FontAntialiasing p_antialiasing) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1137,7 +1161,7 @@ void TextServerFallback::_font_set_antialiasing(const RID &p_font_rid, TextServe
}
TextServer::FontAntialiasing TextServerFallback::_font_get_antialiasing(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TextServer::FONT_ANTIALIASING_NONE);
MutexLock lock(fd->mutex);
@@ -1145,7 +1169,7 @@ TextServer::FontAntialiasing TextServerFallback::_font_get_antialiasing(const RI
}
void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool p_generate_mipmaps) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1161,7 +1185,7 @@ void TextServerFallback::_font_set_generate_mipmaps(const RID &p_font_rid, bool
}
bool TextServerFallback::_font_get_generate_mipmaps(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1169,7 +1193,7 @@ bool TextServerFallback::_font_get_generate_mipmaps(const RID &p_font_rid) const
}
void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID &p_font_rid, bool p_msdf) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1180,7 +1204,7 @@ void TextServerFallback::_font_set_multichannel_signed_distance_field(const RID
}
bool TextServerFallback::_font_is_multichannel_signed_distance_field(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1188,7 +1212,7 @@ bool TextServerFallback::_font_is_multichannel_signed_distance_field(const RID &
}
void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64_t p_msdf_pixel_range) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1199,7 +1223,7 @@ void TextServerFallback::_font_set_msdf_pixel_range(const RID &p_font_rid, int64
}
int64_t TextServerFallback::_font_get_msdf_pixel_range(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1207,7 +1231,7 @@ int64_t TextServerFallback::_font_get_msdf_pixel_range(const RID &p_font_rid) co
}
void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_msdf_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1218,7 +1242,7 @@ void TextServerFallback::_font_set_msdf_size(const RID &p_font_rid, int64_t p_ms
}
int64_t TextServerFallback::_font_get_msdf_size(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1226,7 +1250,7 @@ int64_t TextServerFallback::_font_get_msdf_size(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_fixed_size(const RID &p_font_rid, int64_t p_fixed_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1234,7 +1258,7 @@ void TextServerFallback::_font_set_fixed_size(const RID &p_font_rid, int64_t p_f
}
int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1242,7 +1266,7 @@ int64_t TextServerFallback::_font_get_fixed_size(const RID &p_font_rid) const {
}
void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid, bool p_allow_system_fallback) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1250,7 +1274,7 @@ void TextServerFallback::_font_set_allow_system_fallback(const RID &p_font_rid,
}
bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1258,7 +1282,7 @@ bool TextServerFallback::_font_is_allow_system_fallback(const RID &p_font_rid) c
}
void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool p_force_autohinter) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1269,7 +1293,7 @@ void TextServerFallback::_font_set_force_autohinter(const RID &p_font_rid, bool
}
bool TextServerFallback::_font_is_force_autohinter(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -1277,7 +1301,7 @@ bool TextServerFallback::_font_is_force_autohinter(const RID &p_font_rid) const
}
void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hinting p_hinting) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1288,7 +1312,7 @@ void TextServerFallback::_font_set_hinting(const RID &p_font_rid, TextServer::Hi
}
TextServer::Hinting TextServerFallback::_font_get_hinting(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, HINTING_NONE);
MutexLock lock(fd->mutex);
@@ -1296,7 +1320,7 @@ TextServer::Hinting TextServerFallback::_font_get_hinting(const RID &p_font_rid)
}
void TextServerFallback::_font_set_subpixel_positioning(const RID &p_font_rid, TextServer::SubpixelPositioning p_subpixel) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1304,7 +1328,7 @@ void TextServerFallback::_font_set_subpixel_positioning(const RID &p_font_rid, T
}
TextServer::SubpixelPositioning TextServerFallback::_font_get_subpixel_positioning(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, SUBPIXEL_POSITIONING_DISABLED);
MutexLock lock(fd->mutex);
@@ -1312,7 +1336,7 @@ TextServer::SubpixelPositioning TextServerFallback::_font_get_subpixel_positioni
}
void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_strength) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1323,7 +1347,7 @@ void TextServerFallback::_font_set_embolden(const RID &p_font_rid, double p_stre
}
double TextServerFallback::_font_get_embolden(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1332,28 +1356,39 @@ double TextServerFallback::_font_get_embolden(const RID &p_font_rid) const {
void TextServerFallback::_font_set_spacing(const RID &p_font_rid, SpacingType p_spacing, int64_t p_value) {
ERR_FAIL_INDEX((int)p_spacing, 4);
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_NULL(fd);
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ if (fdv->extra_spacing[p_spacing] != p_value) {
+ fdv->extra_spacing[p_spacing] = p_value;
+ }
+ } else {
+ FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL(fd);
- MutexLock lock(fd->mutex);
- if (fd->extra_spacing[p_spacing] != p_value) {
- _font_clear_cache(fd);
- fd->extra_spacing[p_spacing] = p_value;
+ MutexLock lock(fd->mutex);
+ if (fd->extra_spacing[p_spacing] != p_value) {
+ _font_clear_cache(fd);
+ fd->extra_spacing[p_spacing] = p_value;
+ }
}
}
int64_t TextServerFallback::_font_get_spacing(const RID &p_font_rid, SpacingType p_spacing) const {
ERR_FAIL_INDEX_V((int)p_spacing, 4, 0);
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(p_font_rid);
+ if (fdv) {
+ return fdv->extra_spacing[p_spacing];
+ } else {
+ FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ ERR_FAIL_NULL_V(fd, 0);
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
- ERR_FAIL_NULL_V(fd, 0);
-
- MutexLock lock(fd->mutex);
- return fd->extra_spacing[p_spacing];
+ MutexLock lock(fd->mutex);
+ return fd->extra_spacing[p_spacing];
+ }
}
void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transform2D &p_transform) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1364,7 +1399,7 @@ void TextServerFallback::_font_set_transform(const RID &p_font_rid, const Transf
}
Transform2D TextServerFallback::_font_get_transform(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Transform2D());
MutexLock lock(fd->mutex);
@@ -1372,7 +1407,7 @@ Transform2D TextServerFallback::_font_get_transform(const RID &p_font_rid) const
}
void TextServerFallback::_font_set_variation_coordinates(const RID &p_font_rid, const Dictionary &p_variation_coordinates) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1383,7 +1418,7 @@ void TextServerFallback::_font_set_variation_coordinates(const RID &p_font_rid,
}
Dictionary TextServerFallback::_font_get_variation_coordinates(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -1391,7 +1426,7 @@ Dictionary TextServerFallback::_font_get_variation_coordinates(const RID &p_font
}
void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_oversampling) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1402,7 +1437,7 @@ void TextServerFallback::_font_set_oversampling(const RID &p_font_rid, double p_
}
double TextServerFallback::_font_get_oversampling(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1410,7 +1445,7 @@ double TextServerFallback::_font_get_oversampling(const RID &p_font_rid) const {
}
TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
@@ -1422,7 +1457,7 @@ TypedArray<Vector2i> TextServerFallback::_font_get_size_cache_list(const RID &p_
}
void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1434,7 +1469,7 @@ void TextServerFallback::_font_clear_size_cache(const RID &p_font_rid) {
}
void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Vector2i &p_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1446,7 +1481,7 @@ void TextServerFallback::_font_remove_size_cache(const RID &p_font_rid, const Ve
}
void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size, double p_ascent) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1457,7 +1492,7 @@ void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size,
}
double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1473,7 +1508,7 @@ double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_siz
}
void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size, double p_descent) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
Vector2i size = _get_size(fd, p_size);
@@ -1483,7 +1518,7 @@ void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size
}
double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1499,7 +1534,7 @@ double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_si
}
void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int64_t p_size, double p_underline_position) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1510,7 +1545,7 @@ void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int
}
double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1526,7 +1561,7 @@ double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, i
}
void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, int64_t p_size, double p_underline_thickness) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1537,7 +1572,7 @@ void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, in
}
double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1553,7 +1588,7 @@ double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid,
}
void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size, double p_scale) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1569,7 +1604,7 @@ void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size,
}
double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0.0);
MutexLock lock(fd->mutex);
@@ -1585,7 +1620,7 @@ double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size
}
int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const Vector2i &p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, 0);
MutexLock lock(fd->mutex);
@@ -1597,7 +1632,7 @@ int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const
}
void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
Vector2i size = _get_size_outline(fd, p_size);
@@ -1607,7 +1642,7 @@ void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vecto
}
void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1619,7 +1654,7 @@ void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vecto
}
void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
ERR_FAIL_COND(p_image.is_null());
@@ -1648,7 +1683,7 @@ void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Ve
}
Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Ref<Image>());
MutexLock lock(fd->mutex);
@@ -1662,7 +1697,7 @@ Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, co
void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const PackedInt32Array &p_offsets) {
ERR_FAIL_COND(p_offsets.size() % 4 != 0);
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1681,7 +1716,7 @@ void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const
}
PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
@@ -1706,7 +1741,7 @@ PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font
}
PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid, const Vector2i &p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedInt32Array());
MutexLock lock(fd->mutex);
@@ -1722,7 +1757,7 @@ PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid,
}
void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2i &p_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1733,7 +1768,7 @@ void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2
}
void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1744,7 +1779,7 @@ void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2
}
Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -1782,7 +1817,7 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64
}
void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph, const Vector2 &p_advance) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1797,7 +1832,7 @@ void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t
}
Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -1827,7 +1862,7 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const
}
void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_offset) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1842,7 +1877,7 @@ void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vec
}
Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -1872,7 +1907,7 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve
}
void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Vector2 &p_gl_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1887,7 +1922,7 @@ void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vecto
}
Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Rect2());
MutexLock lock(fd->mutex);
@@ -1912,7 +1947,7 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V
}
void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1927,7 +1962,7 @@ void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve
}
int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, -1);
MutexLock lock(fd->mutex);
@@ -1952,7 +1987,7 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c
}
void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -1967,7 +2002,7 @@ void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, cons
}
RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, RID());
MutexLock lock(fd->mutex);
@@ -2013,7 +2048,7 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const
}
Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Size2());
MutexLock lock(fd->mutex);
@@ -2059,7 +2094,7 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co
}
Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, int64_t p_size, int64_t p_index) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -2109,7 +2144,7 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i
}
TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_font_rid, int64_t p_size) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, TypedArray<Vector2i>());
MutexLock lock(fd->mutex);
@@ -2125,7 +2160,7 @@ TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_fon
}
void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t p_size) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2136,7 +2171,7 @@ void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t
}
void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2147,7 +2182,7 @@ void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_s
}
void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2158,7 +2193,7 @@ void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size
}
Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Vector2());
MutexLock lock(fd->mutex);
@@ -2202,7 +2237,7 @@ int64_t TextServerFallback::_font_get_char_from_glyph_index(const RID &p_font_ri
}
bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_COND_V_MSG((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff), false, "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_char, 16) + ".");
if (!fd) {
return false;
@@ -2223,7 +2258,7 @@ bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) c
}
String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, String());
MutexLock lock(fd->mutex);
@@ -2256,7 +2291,7 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons
}
void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2i &p_size, int64_t p_start, int64_t p_end) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
ERR_FAIL_COND_MSG((p_start >= 0xd800 && p_start <= 0xdfff) || (p_start > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_start, 16) + ".");
ERR_FAIL_COND_MSG((p_end >= 0xd800 && p_end <= 0xdfff) || (p_end > 0x10ffff), "Unicode parsing error: Invalid unicode codepoint " + String::num_int64(p_end, 16) + ".");
@@ -2291,7 +2326,7 @@ void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2
}
void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_index) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2322,7 +2357,7 @@ void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2
}
void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2414,7 +2449,7 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca
}
void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const RID &p_canvas, int64_t p_size, int64_t p_outline_size, const Vector2 &p_pos, int64_t p_index, const Color &p_color) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2506,7 +2541,7 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R
}
bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, const String &p_language) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2518,7 +2553,7 @@ bool TextServerFallback::_font_is_language_supported(const RID &p_font_rid, cons
}
void TextServerFallback::_font_set_language_support_override(const RID &p_font_rid, const String &p_language, bool p_supported) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2526,7 +2561,7 @@ void TextServerFallback::_font_set_language_support_override(const RID &p_font_r
}
bool TextServerFallback::_font_get_language_support_override(const RID &p_font_rid, const String &p_language) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2534,7 +2569,7 @@ bool TextServerFallback::_font_get_language_support_override(const RID &p_font_r
}
void TextServerFallback::_font_remove_language_support_override(const RID &p_font_rid, const String &p_language) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2542,7 +2577,7 @@ void TextServerFallback::_font_remove_language_support_override(const RID &p_fon
}
PackedStringArray TextServerFallback::_font_get_language_support_overrides(const RID &p_font_rid) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
@@ -2554,7 +2589,7 @@ PackedStringArray TextServerFallback::_font_get_language_support_overrides(const
}
bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const String &p_script) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2566,7 +2601,7 @@ bool TextServerFallback::_font_is_script_supported(const RID &p_font_rid, const
}
void TextServerFallback::_font_set_script_support_override(const RID &p_font_rid, const String &p_script, bool p_supported) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2574,7 +2609,7 @@ void TextServerFallback::_font_set_script_support_override(const RID &p_font_rid
}
bool TextServerFallback::_font_get_script_support_override(const RID &p_font_rid, const String &p_script) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, false);
MutexLock lock(fd->mutex);
@@ -2582,7 +2617,7 @@ bool TextServerFallback::_font_get_script_support_override(const RID &p_font_rid
}
void TextServerFallback::_font_remove_script_support_override(const RID &p_font_rid, const String &p_script) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2592,7 +2627,7 @@ void TextServerFallback::_font_remove_script_support_override(const RID &p_font_
}
PackedStringArray TextServerFallback::_font_get_script_support_overrides(const RID &p_font_rid) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, PackedStringArray());
MutexLock lock(fd->mutex);
@@ -2604,7 +2639,7 @@ PackedStringArray TextServerFallback::_font_get_script_support_overrides(const R
}
void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_rid, const Dictionary &p_overrides) {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL(fd);
MutexLock lock(fd->mutex);
@@ -2614,7 +2649,7 @@ void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_
}
Dictionary TextServerFallback::_font_get_opentype_feature_overrides(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -2626,7 +2661,7 @@ Dictionary TextServerFallback::_font_supported_feature_list(const RID &p_font_ri
}
Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_rid) const {
- FontFallback *fd = font_owner.get_or_null(p_font_rid);
+ FontFallback *fd = _get_font_data(p_font_rid);
ERR_FAIL_NULL_V(fd, Dictionary());
MutexLock lock(fd->mutex);
@@ -2904,7 +2939,7 @@ bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const Stri
ERR_FAIL_COND_V(p_size <= 0, false);
for (int i = 0; i < p_fonts.size(); i++) {
- ERR_FAIL_COND_V(!font_owner.get_or_null(p_fonts[i]), false);
+ ERR_FAIL_COND_V(!_get_font_data(p_fonts[i]), false);
}
if (p_text.is_empty()) {
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index c44b45fc27..3b0b10ec35 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -245,6 +245,11 @@ class TextServerFallback : public TextServerExtension {
}
};
+ struct FontFallbackLinkedVariation {
+ RID base_font;
+ int extra_spacing[4] = { 0, 0, 0, 0 };
+ };
+
struct FontFallback {
Mutex mutex;
@@ -451,9 +456,19 @@ class TextServerFallback : public TextServerExtension {
// Common data.
double oversampling = 1.0;
+ mutable RID_PtrOwner<FontFallbackLinkedVariation> font_var_owner;
mutable RID_PtrOwner<FontFallback> font_owner;
mutable RID_PtrOwner<ShapedTextDataFallback> shaped_owner;
+ _FORCE_INLINE_ FontFallback *_get_font_data(const RID &p_font_rid) const {
+ RID rid = p_font_rid;
+ FontFallbackLinkedVariation *fdv = font_var_owner.get_or_null(rid);
+ if (unlikely(fdv)) {
+ rid = fdv->base_font;
+ }
+ return font_owner.get_or_null(rid);
+ }
+
struct SystemFontKey {
String font_name;
TextServer::FontAntialiasing antialiasing = TextServer::FONT_ANTIALIASING_GRAY;
@@ -569,6 +584,7 @@ public:
/* Font interface */
MODBIND0R(RID, create_font);
+ MODBIND1R(RID, create_font_linked_variation, const RID &);
MODBIND2(font_set_data, const RID &, const PackedByteArray &);
MODBIND3(font_set_data_ptr, const RID &, const uint8_t *, int64_t);
diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
index 7cedfa6888..02709d4dc5 100644
--- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
+++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
@@ -91,10 +91,6 @@ open class GodotEditor : GodotActivity() {
private val commandLineParams = ArrayList<String>()
override fun onCreate(savedInstanceState: Bundle?) {
- // We exclude certain permissions from the set we request at startup, as they'll be
- // requested on demand based on use-cases.
- PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
-
val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS)
Log.d(TAG, "Received parameters ${params.contentToString()}")
updateCommandLineParams(params)
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
index 0e111d5247..f819063a22 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt
@@ -928,6 +928,19 @@ class Godot(private val context: Context) : SensorEventListener {
}
/**
+ * Return true if the given feature is supported.
+ */
+ @Keep
+ private fun hasFeature(feature: String): Boolean {
+ for (plugin in pluginRegistry.allPlugins) {
+ if (plugin.supportsFeature(feature)) {
+ return true
+ }
+ }
+ return false
+ }
+
+ /**
* Get the list of gdextension modules to register.
*/
@Keep
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
index 4636f753af..417be7cef4 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotActivity.kt
@@ -30,12 +30,15 @@
package org.godotengine.godot
+import android.Manifest
import android.app.Activity
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import androidx.annotation.CallSuper
import androidx.fragment.app.FragmentActivity
+import org.godotengine.godot.utils.PermissionsUtil
import org.godotengine.godot.utils.ProcessPhoenix
/**
@@ -62,6 +65,10 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
private set
override fun onCreate(savedInstanceState: Bundle?) {
+ // We exclude certain permissions from the set we request at startup, as they'll be
+ // requested on demand based on use-cases.
+ PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
+
super.onCreate(savedInstanceState)
setContentView(R.layout.godot_app_layout)
@@ -148,6 +155,14 @@ abstract class GodotActivity : FragmentActivity(), GodotHost {
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
godotFragment?.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+ if (requestCode == PermissionsUtil.REQUEST_ALL_PERMISSION_REQ_CODE) {
+ Log.d(TAG, "Received permissions request result..")
+ for (i in permissions.indices) {
+ val permissionGranted = grantResults[i] == PackageManager.PERMISSION_GRANTED
+ Log.d(TAG, "Permission ${permissions[i]} ${if (permissionGranted) { "granted"} else { "denied" }}")
+ }
+ }
}
override fun onBackPressed() {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
index 7f3a3ac7a3..c0912ca4dc 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/plugin/GodotPlugin.java
@@ -316,6 +316,15 @@ public abstract class GodotPlugin {
}
/**
+ * Returns whether the plugin supports the given feature tag.
+ *
+ * @see <a href="https://docs.godotengine.org/en/stable/tutorials/export/feature_tags.html">Feature tags</a>
+ */
+ public boolean supportsFeature(String featureTag) {
+ return false;
+ }
+
+ /**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
index 8353fc8dc6..9a82204467 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
@@ -160,6 +160,7 @@ public final class PermissionsUtil {
try {
if (manifestPermission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
+ Log.d(TAG, "Requesting permission " + manifestPermission);
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse(String.format("package:%s", activity.getPackageName())));
@@ -173,6 +174,7 @@ public final class PermissionsUtil {
PermissionInfo permissionInfo = getPermissionInfo(activity, manifestPermission);
int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, manifestPermission) != PackageManager.PERMISSION_GRANTED) {
+ Log.d(TAG, "Requesting permission " + manifestPermission);
requestedPermissions.add(manifestPermission);
}
}
diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp
index 1703179b8e..cb6ebf14a8 100644
--- a/platform/android/java_godot_wrapper.cpp
+++ b/platform/android/java_godot_wrapper.cpp
@@ -82,6 +82,7 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_
_end_benchmark_measure = p_env->GetMethodID(godot_class, "nativeEndBenchmarkMeasure", "(Ljava/lang/String;)V");
_dump_benchmark = p_env->GetMethodID(godot_class, "nativeDumpBenchmark", "(Ljava/lang/String;)V");
_get_gdextension_list_config_file = p_env->GetMethodID(godot_class, "getGDExtensionConfigFiles", "()[Ljava/lang/String;");
+ _has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z");
}
GodotJavaWrapper::~GodotJavaWrapper() {
@@ -373,3 +374,15 @@ void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) {
env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file);
}
}
+
+bool GodotJavaWrapper::has_feature(const String &p_feature) const {
+ if (_has_feature) {
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_NULL_V(env, false);
+
+ jstring j_feature = env->NewStringUTF(p_feature.utf8().get_data());
+ return env->CallBooleanMethod(godot_instance, _has_feature, j_feature);
+ } else {
+ return false;
+ }
+}
diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h
index f427a2937c..7c6327c9e1 100644
--- a/platform/android/java_godot_wrapper.h
+++ b/platform/android/java_godot_wrapper.h
@@ -73,6 +73,7 @@ private:
jmethodID _begin_benchmark_measure = nullptr;
jmethodID _end_benchmark_measure = nullptr;
jmethodID _dump_benchmark = nullptr;
+ jmethodID _has_feature = nullptr;
public:
GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance);
@@ -110,6 +111,9 @@ public:
// Return the list of gdextensions config file.
Vector<String> get_gdextension_list_config_file() const;
+
+ // Return true if the given feature is supported.
+ bool has_feature(const String &p_feature) const;
};
#endif // JAVA_GODOT_WRAPPER_H
diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp
index 8f80516a9f..df8a9893c3 100644
--- a/platform/android/os_android.cpp
+++ b/platform/android/os_android.cpp
@@ -749,6 +749,11 @@ bool OS_Android::_check_internal_feature_support(const String &p_feature) {
return true;
}
#endif
+
+ if (godot_java->has_feature(p_feature)) {
+ return true;
+ }
+
return false;
}
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp
index 91c14e0e91..cf4354139a 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.cpp
+++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp
@@ -142,36 +142,40 @@ void FreeDesktopPortalDesktop::append_dbus_string(DBusMessageIter *p_iter, const
}
}
-void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters) {
+void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts) {
DBusMessageIter dict_iter;
DBusMessageIter var_iter;
DBusMessageIter arr_iter;
const char *filters_key = "filters";
+ ERR_FAIL_COND(p_filter_names.size() != p_filter_exts.size());
+
dbus_message_iter_open_container(p_iter, DBUS_TYPE_DICT_ENTRY, nullptr, &dict_iter);
dbus_message_iter_append_basic(&dict_iter, DBUS_TYPE_STRING, &filters_key);
dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_VARIANT, "a(sa(us))", &var_iter);
dbus_message_iter_open_container(&var_iter, DBUS_TYPE_ARRAY, "(sa(us))", &arr_iter);
- for (int i = 0; i < p_filters.size(); i++) {
- Vector<String> tokens = p_filters[i].split(";");
- if (tokens.size() == 2) {
- DBusMessageIter struct_iter;
- DBusMessageIter array_iter;
- DBusMessageIter array_struct_iter;
- dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
- append_dbus_string(&struct_iter, tokens[0]);
-
- dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
+ for (int i = 0; i < p_filter_names.size(); i++) {
+ DBusMessageIter struct_iter;
+ DBusMessageIter array_iter;
+ DBusMessageIter array_struct_iter;
+ dbus_message_iter_open_container(&arr_iter, DBUS_TYPE_STRUCT, nullptr, &struct_iter);
+ append_dbus_string(&struct_iter, p_filter_names[i]);
+
+ dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter);
+ String flt = p_filter_exts[i];
+ int filter_slice_count = flt.get_slice_count(",");
+ for (int j = 0; j < filter_slice_count; j++) {
dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter);
+ String str = (flt.get_slice(",", j).strip_edges());
{
const unsigned nil = 0;
dbus_message_iter_append_basic(&array_struct_iter, DBUS_TYPE_UINT32, &nil);
}
- append_dbus_string(&array_struct_iter, tokens[1]);
+ append_dbus_string(&array_struct_iter, str);
dbus_message_iter_close_container(&array_iter, &array_struct_iter);
- dbus_message_iter_close_container(&struct_iter, &array_iter);
- dbus_message_iter_close_container(&arr_iter, &struct_iter);
}
+ dbus_message_iter_close_container(&struct_iter, &array_iter);
+ dbus_message_iter_close_container(&arr_iter, &struct_iter);
}
dbus_message_iter_close_container(&var_iter, &arr_iter);
dbus_message_iter_close_container(&dict_iter, &var_iter);
@@ -219,7 +223,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_bool(DBusMessageIter *p_iter, co
dbus_message_iter_close_container(p_iter, &dict_iter);
}
-bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls) {
+bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index) {
ERR_FAIL_COND_V(dbus_message_iter_get_arg_type(p_iter) != DBUS_TYPE_UINT32, false);
dbus_uint32_t resp_code;
@@ -243,7 +247,22 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
DBusMessageIter var_iter;
dbus_message_iter_recurse(&iter, &var_iter);
- if (strcmp(key, "uris") == 0) {
+ if (strcmp(key, "current_filter") == 0) { // (sa(us))
+ if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_STRUCT) {
+ DBusMessageIter struct_iter;
+ dbus_message_iter_recurse(&var_iter, &struct_iter);
+ while (dbus_message_iter_get_arg_type(&struct_iter) == DBUS_TYPE_STRING) {
+ const char *value;
+ dbus_message_iter_get_basic(&struct_iter, &value);
+ String name = String::utf8(value);
+
+ r_index = p_names.find(name);
+ if (!dbus_message_iter_next(&struct_iter)) {
+ break;
+ }
+ }
+ }
+ } else if (strcmp(key, "uris") == 0) { // as
if (dbus_message_iter_get_arg_type(&var_iter) == DBUS_TYPE_ARRAY) {
DBusMessageIter uri_iter;
dbus_message_iter_recurse(&var_iter, &uri_iter);
@@ -266,17 +285,43 @@ bool FreeDesktopPortalDesktop::file_chooser_parse_response(DBusMessageIter *p_it
return true;
}
-Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+Error FreeDesktopPortalDesktop::file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
if (unsupported) {
return FAILED;
}
+ ERR_FAIL_INDEX_V(int(p_mode), DisplayServer::FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
+ Vector<String> filter_names;
+ Vector<String> filter_exts;
+ for (int i = 0; i < p_filters.size(); i++) {
+ Vector<String> tokens = p_filters[i].split(";");
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ if (!flt.is_empty()) {
+ if (tokens.size() == 2) {
+ filter_exts.push_back(flt);
+ filter_names.push_back(tokens[1]);
+ } else {
+ filter_exts.push_back(flt);
+ filter_names.push_back(flt);
+ }
+ }
+ }
+ }
+ if (filter_names.is_empty()) {
+ filter_exts.push_back("*.*");
+ filter_names.push_back(RTR("All Files"));
+ }
+
DBusError err;
dbus_error_init(&err);
// Open connection and add signal handler.
FileDialogData fd;
fd.callback = p_callback;
+ fd.prev_focus = p_window_id;
+ fd.filter_names = filter_names;
CryptoCore::RandomGenerator rng;
ERR_FAIL_COND_V_MSG(rng.init(), FAILED, "Failed to initialize random number generator.");
@@ -307,16 +352,10 @@ Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const Stri
// Generate FileChooser message.
const char *method = nullptr;
- switch (p_mode) {
- case DisplayServer::FILE_DIALOG_MODE_SAVE_FILE: {
- method = "SaveFile";
- } break;
- case DisplayServer::FILE_DIALOG_MODE_OPEN_ANY:
- case DisplayServer::FILE_DIALOG_MODE_OPEN_FILE:
- case DisplayServer::FILE_DIALOG_MODE_OPEN_DIR:
- case DisplayServer::FILE_DIALOG_MODE_OPEN_FILES: {
- method = "OpenFile";
- } break;
+ if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
+ method = "SaveFile";
+ } else {
+ method = "OpenFile";
}
DBusMessage *message = dbus_message_new_method_call(BUS_OBJECT_NAME, BUS_OBJECT_PATH, BUS_INTERFACE_FILE_CHOOSER, method);
@@ -333,7 +372,7 @@ Error FreeDesktopPortalDesktop::file_dialog_show(const String &p_xid, const Stri
append_dbus_dict_string(&arr_iter, "handle_token", token);
append_dbus_dict_bool(&arr_iter, "multiple", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_FILES);
append_dbus_dict_bool(&arr_iter, "directory", p_mode == DisplayServer::FILE_DIALOG_MODE_OPEN_DIR);
- append_dbus_dict_filters(&arr_iter, p_filters);
+ append_dbus_dict_filters(&arr_iter, filter_names, filter_exts);
append_dbus_dict_string(&arr_iter, "current_folder", p_current_directory, true);
if (p_mode == DisplayServer::FILE_DIALOG_MODE_SAVE_FILE) {
append_dbus_dict_string(&arr_iter, "current_name", p_filename);
@@ -408,13 +447,18 @@ void FreeDesktopPortalDesktop::_thread_file_dialog_monitor(void *p_ud) {
if (dbus_message_iter_init(msg, &iter)) {
bool cancel = false;
Vector<String> uris;
- file_chooser_parse_response(&iter, cancel, uris);
+ int index = 0;
+ file_chooser_parse_response(&iter, fd.filter_names, cancel, uris, index);
if (fd.callback.is_valid()) {
Variant v_status = !cancel;
Variant v_files = uris;
- Variant *v_args[2] = { &v_status, &v_files };
- fd.callback.call_deferredp((const Variant **)&v_args, 2);
+ Variant v_index = index;
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ fd.callback.call_deferredp((const Variant **)&v_args, 3);
+ }
+ if (fd.prev_focus != DisplayServer::INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(fd.prev_focus);
}
}
dbus_message_unref(msg);
diff --git a/platform/linuxbsd/freedesktop_portal_desktop.h b/platform/linuxbsd/freedesktop_portal_desktop.h
index a9b83b3844..503c382207 100644
--- a/platform/linuxbsd/freedesktop_portal_desktop.h
+++ b/platform/linuxbsd/freedesktop_portal_desktop.h
@@ -49,13 +49,15 @@ private:
bool read_setting(const char *p_namespace, const char *p_key, int p_type, void *r_value);
static void append_dbus_string(DBusMessageIter *p_iter, const String &p_string);
- static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filters);
+ static void append_dbus_dict_filters(DBusMessageIter *p_iter, const Vector<String> &p_filter_names, const Vector<String> &p_filter_exts);
static void append_dbus_dict_string(DBusMessageIter *p_iter, const String &p_key, const String &p_value, bool p_as_byte_array = false);
static void append_dbus_dict_bool(DBusMessageIter *p_iter, const String &p_key, bool p_value);
- static bool file_chooser_parse_response(DBusMessageIter *p_iter, bool &r_cancel, Vector<String> &r_urls);
+ static bool file_chooser_parse_response(DBusMessageIter *p_iter, const Vector<String> &p_names, bool &r_cancel, Vector<String> &r_urls, int &r_index);
struct FileDialogData {
+ Vector<String> filter_names;
DBusConnection *connection = nullptr;
+ DisplayServer::WindowID prev_focus = DisplayServer::INVALID_WINDOW_ID;
Callable callback;
String path;
};
@@ -73,7 +75,7 @@ public:
bool is_supported() { return !unsupported; }
- Error file_dialog_show(const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
+ Error file_dialog_show(DisplayServer::WindowID p_window_id, const String &p_xid, const String &p_title, const String &p_current_directory, const String &p_filename, DisplayServer::FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
// Retrieve the system's preferred color scheme.
// 0: No preference or unknown.
diff --git a/platform/linuxbsd/platform_gl.h b/platform/linuxbsd/platform_gl.h
index 1c19c4518a..5d4c2d8978 100644
--- a/platform/linuxbsd/platform_gl.h
+++ b/platform/linuxbsd/platform_gl.h
@@ -35,6 +35,10 @@
#define GL_API_ENABLED // Allow using desktop GL.
#endif
+#ifndef GLES_API_ENABLED
+#define GLES_API_ENABLED // Allow using GLES.
+#endif
+
#include "thirdparty/glad/glad/egl.h"
#include "thirdparty/glad/glad/gl.h"
diff --git a/platform/linuxbsd/x11/SCsub b/platform/linuxbsd/x11/SCsub
index a4890391ce..bbfaaf10d1 100644
--- a/platform/linuxbsd/x11/SCsub
+++ b/platform/linuxbsd/x11/SCsub
@@ -25,7 +25,9 @@ if env["vulkan"]:
if env["opengl3"]:
env.Append(CPPDEFINES=["GLAD_GLX_NO_X11"])
- source_files.append(["gl_manager_x11.cpp", "detect_prime_x11.cpp", "#thirdparty/glad/glx.c"])
+ source_files.append(
+ ["gl_manager_x11_egl.cpp", "gl_manager_x11.cpp", "detect_prime_x11.cpp", "#thirdparty/glad/glx.c"]
+ )
objects = []
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 897a6438d2..31846c80a2 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -364,14 +364,14 @@ bool DisplayServerX11::is_dark_mode() const {
}
Error DisplayServerX11::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
- WindowID window_id = _get_focused_window_or_popup();
+ WindowID window_id = last_focused_window;
if (!windows.has(window_id)) {
window_id = MAIN_WINDOW_ID;
}
String xid = vformat("x11:%x", (uint64_t)windows[window_id].x11_window);
- return portal_desktop->file_dialog_show(xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback);
+ return portal_desktop->file_dialog_show(last_focused_window, xid, p_title, p_current_directory, p_filename, p_mode, p_filters, p_callback);
}
#endif
@@ -1495,6 +1495,9 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
if (gl_manager) {
gl_manager->window_destroy(p_id);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_destroy(p_id);
+ }
#endif
if (wd.xic) {
@@ -1534,6 +1537,9 @@ int64_t DisplayServerX11::window_get_native_handle(HandleType p_handle_type, Win
if (gl_manager) {
return (int64_t)gl_manager->get_glx_context(p_window);
}
+ if (gl_manager_egl) {
+ return (int64_t)gl_manager_egl->get_context(p_window);
+ }
return 0;
}
#endif
@@ -1711,6 +1717,9 @@ void DisplayServerX11::gl_window_make_current(DisplayServer::WindowID p_window_i
if (gl_manager) {
gl_manager->window_make_current(p_window_id);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_make_current(p_window_id);
+ }
#endif
}
@@ -2012,6 +2021,9 @@ void DisplayServerX11::window_set_size(const Size2i p_size, WindowID p_window) {
if (gl_manager) {
gl_manager->window_resize(p_window, xwa.width, xwa.height);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_resize(p_window, xwa.width, xwa.height);
+ }
#endif
}
@@ -3721,6 +3733,9 @@ void DisplayServerX11::_window_changed(XEvent *event) {
if (gl_manager) {
gl_manager->window_resize(window_id, wd.size.width, wd.size.height);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_resize(window_id, wd.size.width, wd.size.height);
+ }
#endif
if (!wd.rect_changed_callback.is_null()) {
@@ -4855,6 +4870,9 @@ void DisplayServerX11::release_rendering_thread() {
if (gl_manager) {
gl_manager->release_current();
}
+ if (gl_manager_egl) {
+ gl_manager_egl->release_current();
+ }
#endif
}
@@ -4863,6 +4881,9 @@ void DisplayServerX11::make_rendering_thread() {
if (gl_manager) {
gl_manager->make_current();
}
+ if (gl_manager_egl) {
+ gl_manager_egl->make_current();
+ }
#endif
}
@@ -4871,6 +4892,9 @@ void DisplayServerX11::swap_buffers() {
if (gl_manager) {
gl_manager->swap_buffers();
}
+ if (gl_manager_egl) {
+ gl_manager_egl->swap_buffers();
+ }
#endif
}
@@ -5024,6 +5048,9 @@ void DisplayServerX11::window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mo
if (gl_manager) {
gl_manager->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->set_use_vsync(p_vsync_mode != DisplayServer::VSYNC_DISABLED);
+ }
#endif
}
@@ -5038,6 +5065,9 @@ DisplayServer::VSyncMode DisplayServerX11::window_get_vsync_mode(WindowID p_wind
if (gl_manager) {
return gl_manager->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;
}
+ if (gl_manager_egl) {
+ return gl_manager_egl->is_using_vsync() ? DisplayServer::VSYNC_ENABLED : DisplayServer::VSYNC_DISABLED;
+ }
#endif
return DisplayServer::VSYNC_ENABLED;
}
@@ -5050,6 +5080,7 @@ Vector<String> DisplayServerX11::get_rendering_drivers_func() {
#endif
#ifdef GLES3_ENABLED
drivers.push_back("opengl3");
+ drivers.push_back("opengl3_es");
#endif
return drivers;
@@ -5092,6 +5123,21 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't acquire visual info from display.");
vi_selected = true;
}
+ if (gl_manager_egl) {
+ XVisualInfo visual_info_template;
+ int visual_id = gl_manager_egl->display_get_native_visual_id(x11_display);
+ ERR_FAIL_COND_V_MSG(visual_id < 0, INVALID_WINDOW_ID, "Unable to get a visual id.");
+
+ visual_info_template.visualid = (VisualID)visual_id;
+
+ int number_of_visuals = 0;
+ XVisualInfo *vi_list = XGetVisualInfo(x11_display, VisualIDMask, &visual_info_template, &number_of_visuals);
+ ERR_FAIL_COND_V(number_of_visuals <= 0, INVALID_WINDOW_ID);
+
+ visualInfo = vi_list[0];
+
+ XFree(vi_list);
+ }
#endif
if (!vi_selected) {
@@ -5364,8 +5410,12 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
if (gl_manager) {
Error err = gl_manager->window_create(id, wd.x11_window, x11_display, win_rect.size.width, win_rect.size.height);
ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Can't create an OpenGL window");
- window_set_vsync_mode(p_vsync_mode, id);
}
+ if (gl_manager_egl) {
+ Error err = gl_manager_egl->window_create(id, x11_display, &wd.x11_window, win_rect.size.width, win_rect.size.height);
+ ERR_FAIL_COND_V_MSG(err != OK, INVALID_WINDOW_ID, "Failed to create an OpenGLES window.");
+ }
+ window_set_vsync_mode(p_vsync_mode, id);
#endif
//set_class_hint(x11_display, wd.x11_window);
@@ -5766,7 +5816,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
#endif
// Initialize context and rendering device.
#if defined(GLES3_ENABLED)
- if (rendering_driver == "opengl3") {
+ if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") {
if (getenv("DRI_PRIME") == nullptr) {
int use_prime = -1;
@@ -5807,7 +5857,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
setenv("DRI_PRIME", "1", 1);
}
}
-
+ }
+ if (rendering_driver == "opengl3") {
GLManager_X11::ContextType opengl_api_type = GLManager_X11::GLES_3_0_COMPATIBLE;
gl_manager = memnew(GLManager_X11(p_resolution, opengl_api_type));
@@ -5820,14 +5871,20 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
}
driver_found = true;
- if (true) {
- RasterizerGLES3::make_current(true);
- } else {
- memdelete(gl_manager);
- gl_manager = nullptr;
+ RasterizerGLES3::make_current(true);
+ }
+ if (rendering_driver == "opengl3_es") {
+ gl_manager_egl = memnew(GLManagerEGL_X11);
+
+ if (gl_manager_egl->initialize() != OK) {
+ memdelete(gl_manager_egl);
+ gl_manager_egl = nullptr;
r_error = ERR_UNAVAILABLE;
return;
}
+ driver_found = true;
+
+ RasterizerGLES3::make_current(false);
}
#endif
if (!driver_found) {
@@ -6044,6 +6101,9 @@ DisplayServerX11::~DisplayServerX11() {
if (gl_manager) {
gl_manager->window_destroy(E.key);
}
+ if (gl_manager_egl) {
+ gl_manager_egl->window_destroy(E.key);
+ }
#endif
WindowData &wd = E.value;
@@ -6094,6 +6154,10 @@ DisplayServerX11::~DisplayServerX11() {
memdelete(gl_manager);
gl_manager = nullptr;
}
+ if (gl_manager_egl) {
+ memdelete(gl_manager_egl);
+ gl_manager_egl = nullptr;
+ }
#endif
if (xrandr_handle) {
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index 71beddce76..9706a4aa11 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -54,6 +54,7 @@
#if defined(GLES3_ENABLED)
#include "x11/gl_manager_x11.h"
+#include "x11/gl_manager_x11_egl.h"
#endif
#if defined(VULKAN_ENABLED)
@@ -138,6 +139,7 @@ class DisplayServerX11 : public DisplayServer {
#if defined(GLES3_ENABLED)
GLManager_X11 *gl_manager = nullptr;
+ GLManagerEGL_X11 *gl_manager_egl = nullptr;
#endif
#if defined(VULKAN_ENABLED)
VulkanContextX11 *context_vulkan = nullptr;
diff --git a/platform/linuxbsd/x11/gl_manager_x11_egl.cpp b/platform/linuxbsd/x11/gl_manager_x11_egl.cpp
new file mode 100644
index 0000000000..544684ebf1
--- /dev/null
+++ b/platform/linuxbsd/x11/gl_manager_x11_egl.cpp
@@ -0,0 +1,63 @@
+/**************************************************************************/
+/* gl_manager_x11_egl.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "gl_manager_x11_egl.h"
+
+#if defined(X11_ENABLED) && defined(GLES3_ENABLED)
+
+#include <stdio.h>
+#include <stdlib.h>
+
+const char *GLManagerEGL_X11::_get_platform_extension_name() const {
+ return "EGL_KHR_platform_x11";
+}
+
+EGLenum GLManagerEGL_X11::_get_platform_extension_enum() const {
+ return EGL_PLATFORM_X11_KHR;
+}
+
+Vector<EGLAttrib> GLManagerEGL_X11::_get_platform_display_attributes() const {
+ return Vector<EGLAttrib>();
+}
+
+EGLenum GLManagerEGL_X11::_get_platform_api_enum() const {
+ return EGL_OPENGL_ES_API;
+}
+
+Vector<EGLint> GLManagerEGL_X11::_get_platform_context_attribs() const {
+ Vector<EGLint> ret;
+ ret.push_back(EGL_CONTEXT_CLIENT_VERSION);
+ ret.push_back(3);
+ ret.push_back(EGL_NONE);
+
+ return ret;
+}
+
+#endif // WINDOWS_ENABLED && GLES3_ENABLED
diff --git a/platform/linuxbsd/x11/gl_manager_x11_egl.h b/platform/linuxbsd/x11/gl_manager_x11_egl.h
new file mode 100644
index 0000000000..b9c96619c4
--- /dev/null
+++ b/platform/linuxbsd/x11/gl_manager_x11_egl.h
@@ -0,0 +1,61 @@
+/**************************************************************************/
+/* gl_manager_x11_egl.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef GL_MANAGER_X11_EGL_H
+#define GL_MANAGER_X11_EGL_H
+
+#if defined(X11_ENABLED) && defined(GLES3_ENABLED)
+
+#include "core/error/error_list.h"
+#include "core/os/os.h"
+#include "core/templates/local_vector.h"
+#include "drivers/egl/egl_manager.h"
+#include "servers/display_server.h"
+
+#include <X11/Xlib.h>
+
+class GLManagerEGL_X11 : public EGLManager {
+private:
+ virtual const char *_get_platform_extension_name() const override;
+ virtual EGLenum _get_platform_extension_enum() const override;
+ virtual EGLenum _get_platform_api_enum() const override;
+ virtual Vector<EGLAttrib> _get_platform_display_attributes() const override;
+ virtual Vector<EGLint> _get_platform_context_attribs() const override;
+
+public:
+ void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height) {}
+
+ GLManagerEGL_X11(){};
+ ~GLManagerEGL_X11(){};
+};
+
+#endif // X11_ENABLED && GLES3_ENABLED
+
+#endif // GL_MANAGER_X11_EGL_H
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index c03b4765f8..2ca9e493b7 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -139,7 +139,14 @@ private:
NSMenu *apple_menu = nullptr;
NSMenu *dock_menu = nullptr;
- HashMap<String, NSMenu *> submenu;
+ struct MenuData {
+ Callable open;
+ Callable close;
+ NSMenu *menu = nullptr;
+ bool is_open = false;
+ };
+ HashMap<String, MenuData> submenu;
+ HashMap<NSMenu *, String> submenu_inv;
struct WarpEvent {
NSTimeInterval timestamp;
@@ -197,6 +204,7 @@ private:
const NSMenu *_get_menu_root(const String &p_menu_root) const;
NSMenu *_get_menu_root(const String &p_menu_root);
+ bool _is_menu_opened(NSMenu *p_menu) const;
WindowID _create_window(WindowMode p_mode, VSyncMode p_vsync_mode, const Rect2i &p_rect);
void _update_window_style(WindowData p_wd);
@@ -223,6 +231,8 @@ private:
public:
NSMenu *get_dock_menu() const;
void menu_callback(id p_sender);
+ void menu_open(NSMenu *p_menu);
+ void menu_close(NSMenu *p_menu);
bool has_window(WindowID p_window) const;
WindowData &get_window(WindowID p_window);
@@ -245,6 +255,7 @@ public:
WindowID _get_focused_window_or_popup() const;
void mouse_enter_window(WindowID p_window);
void mouse_exit_window(WindowID p_window);
+ void update_presentation_mode();
void window_destroy(WindowID p_window);
void window_resize(WindowID p_window, int p_width, int p_height);
@@ -253,6 +264,8 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
+ virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable()) override;
+
virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
@@ -276,6 +289,7 @@ public:
virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const override;
virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const override;
virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const override;
+ virtual bool global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const override;
virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const override;
virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override;
virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override;
@@ -287,11 +301,13 @@ public:
virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) override;
+ virtual void global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) override;
virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag) override;
virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text) override;
virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu) override;
virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode) override;
virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled) override;
+ virtual void global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) override;
virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) override;
virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override;
virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override;
@@ -368,6 +384,7 @@ public:
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_title_size(const String &p_title, WindowID p_window) const override;
virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index 56cb4938ec..67d6f4214f 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -75,7 +75,7 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons
} else {
// Submenu.
if (submenu.has(p_menu_root)) {
- menu = submenu[p_menu_root];
+ menu = submenu[p_menu_root].menu;
}
}
if (menu == apple_menu) {
@@ -99,9 +99,10 @@ NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) {
NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
[n_menu setAutoenablesItems:NO];
[n_menu setDelegate:menu_delegate];
- submenu[p_menu_root] = n_menu;
+ submenu[p_menu_root].menu = n_menu;
+ submenu_inv[n_menu] = p_menu_root;
}
- menu = submenu[p_menu_root];
+ menu = submenu[p_menu_root].menu;
}
if (menu == apple_menu) {
// Do not allow to change Apple menu.
@@ -609,6 +610,30 @@ NSMenu *DisplayServerMacOS::get_dock_menu() const {
return dock_menu;
}
+void DisplayServerMacOS::menu_open(NSMenu *p_menu) {
+ if (submenu_inv.has(p_menu)) {
+ MenuData &md = submenu[submenu_inv[p_menu]];
+ md.is_open = true;
+ if (md.open.is_valid()) {
+ Variant ret;
+ Callable::CallError ce;
+ md.open.callp(nullptr, 0, ret, ce);
+ }
+ }
+}
+
+void DisplayServerMacOS::menu_close(NSMenu *p_menu) {
+ if (submenu_inv.has(p_menu)) {
+ MenuData &md = submenu[submenu_inv[p_menu]];
+ md.is_open = false;
+ if (md.close.is_valid()) {
+ Variant ret;
+ Callable::CallError ce;
+ md.close.callp(nullptr, 0, ret, ce);
+ }
+ }
+}
+
void DisplayServerMacOS::menu_callback(id p_sender) {
if (![p_sender representedObject]) {
return;
@@ -749,6 +774,7 @@ void DisplayServerMacOS::window_destroy(WindowID p_window) {
}
#endif
windows.erase(p_window);
+ update_presentation_mode();
}
void DisplayServerMacOS::window_resize(WindowID p_window, int p_width, int p_height) {
@@ -815,6 +841,24 @@ bool DisplayServerMacOS::_has_help_menu() const {
}
}
+bool DisplayServerMacOS::_is_menu_opened(NSMenu *p_menu) const {
+ if (submenu_inv.has(p_menu)) {
+ const MenuData &md = submenu[submenu_inv[p_menu]];
+ if (md.is_open) {
+ return true;
+ }
+ }
+ for (NSInteger i = (p_menu == [NSApp mainMenu]) ? 1 : 0; i < [p_menu numberOfItems]; i++) {
+ const NSMenuItem *menu_item = [p_menu itemAtIndex:i];
+ if ([menu_item submenu]) {
+ if (_is_menu_opened([menu_item submenu])) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) {
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
@@ -998,6 +1042,23 @@ int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_roo
return out;
}
+void DisplayServerMacOS::global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback, const Callable &p_close_callback) {
+ _THREAD_SAFE_METHOD_
+
+ if (p_menu_root != "" && p_menu_root.to_lower() != "_main" && p_menu_root.to_lower() != "_dock") {
+ // Submenu.
+ if (!submenu.has(p_menu_root)) {
+ NSMenu *n_menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:p_menu_root.utf8().get_data()]];
+ [n_menu setAutoenablesItems:NO];
+ [n_menu setDelegate:menu_delegate];
+ submenu[p_menu_root].menu = n_menu;
+ submenu_inv[n_menu] = p_menu_root;
+ }
+ submenu[p_menu_root].open = p_open_callback;
+ submenu[p_menu_root].close = p_close_callback;
+ }
+}
+
int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
_THREAD_SAFE_METHOD_
@@ -1251,13 +1312,9 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo
ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- const NSMenu *sub_menu = [menu_item submenu];
- if (sub_menu) {
- for (const KeyValue<String, NSMenu *> &E : submenu) {
- if (E.value == sub_menu) {
- return E.key;
- }
- }
+ NSMenu *sub_menu = [menu_item submenu];
+ if (sub_menu && submenu_inv.has(sub_menu)) {
+ return submenu_inv[sub_menu];
}
}
}
@@ -1318,6 +1375,24 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root,
return false;
}
+bool DisplayServerMacOS::global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return [menu_item isHidden];
+ }
+ }
+ return false;
+}
+
String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const {
_THREAD_SAFE_METHOD_
@@ -1497,6 +1572,25 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root
}
}
+void DisplayServerMacOS::global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
+ obj->hover_callback = p_callback;
+ }
+ }
+}
+
void DisplayServerMacOS::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) {
_THREAD_SAFE_METHOD_
@@ -1556,6 +1650,23 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root,
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu && p_submenu.is_empty()) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
+ [menu setSubmenu:nil forItem:menu_item];
+ }
+ return;
+ }
+
NSMenu *sub_menu = _get_menu_root(p_submenu);
if (menu && sub_menu) {
if (sub_menu == menu) {
@@ -1590,9 +1701,13 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
- [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
- [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ if (p_keycode == Key::NONE) {
+ [menu_item setKeyEquivalent:@""];
+ } else {
+ [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
+ String keycode = KeyMappingMacOS::keycode_get_native_string(p_keycode & KeyModifierMask::CODE_MASK);
+ [menu_item setKeyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ }
}
}
}
@@ -1614,6 +1729,23 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root
}
}
+void DisplayServerMacOS::global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setHidden:p_hidden];
+ }
+ }
+}
+
void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) {
_THREAD_SAFE_METHOD_
@@ -1741,6 +1873,11 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int
p_idx++;
}
ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if ([menu_item submenu] && _is_menu_opened([menu_item submenu])) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
[menu removeItemAtIndex:p_idx];
}
}
@@ -1750,6 +1887,10 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ if (_is_menu_opened(menu)) {
+ ERR_PRINT("Can't remove open menu!");
+ return;
+ }
[menu removeAllItems];
// Restore Apple menu.
if (menu == [NSApp mainMenu]) {
@@ -1757,6 +1898,7 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
[menu setSubmenu:apple_menu forItem:menu_item];
}
if (submenu.has(p_menu_root)) {
+ submenu_inv.erase(submenu[p_menu_root].menu);
submenu.erase(p_menu_root);
}
}
@@ -1870,171 +2012,275 @@ Error DisplayServerMacOS::dialog_show(String p_title, String p_description, Vect
return OK;
}
-Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
- _THREAD_SAFE_METHOD_
+@interface FileDialogDropdown : NSObject {
+ NSSavePanel *dialog;
+ NSMutableArray *allowed_types;
+ int cur_index;
+}
+
+- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types;
+- (void)popupAction:(id)sender;
+- (int)getIndex;
+
+@end
+
+@implementation FileDialogDropdown
+
+- (int)getIndex {
+ return cur_index;
+}
+
+- (instancetype)initWithDialog:(NSSavePanel *)p_dialog fileTypes:(NSMutableArray *)p_allowed_types {
+ if ((self = [super init])) {
+ dialog = p_dialog;
+ allowed_types = p_allowed_types;
+ cur_index = 0;
+ }
+ return self;
+}
+
+- (void)popupAction:(id)sender {
+ NSUInteger index = [sender indexOfSelectedItem];
+ if (index < [allowed_types count]) {
+ [dialog setAllowedFileTypes:[allowed_types objectAtIndex:index]];
+ cur_index = index;
+ } else {
+ [dialog setAllowedFileTypes:@[]];
+ cur_index = -1;
+ }
+}
+
+@end
+
+FileDialogDropdown *_make_accessory_view(NSSavePanel *p_panel, const Vector<String> &p_filters) {
+ NSView *group = [[NSView alloc] initWithFrame:NSZeroRect];
+ group.translatesAutoresizingMaskIntoConstraints = NO;
+
+ NSTextField *label = [NSTextField labelWithString:[NSString stringWithUTF8String:RTR("Format").utf8().get_data()]];
+ label.translatesAutoresizingMaskIntoConstraints = NO;
+ if (@available(macOS 10.14, *)) {
+ label.textColor = NSColor.secondaryLabelColor;
+ }
+ if (@available(macOS 11.10, *)) {
+ label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+ }
+ [group addSubview:label];
+
+ NSPopUpButton *popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
+ popup.translatesAutoresizingMaskIntoConstraints = NO;
- NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()];
NSMutableArray *allowed_types = [[NSMutableArray alloc] init];
bool allow_other = false;
for (int i = 0; i < p_filters.size(); i++) {
Vector<String> tokens = p_filters[i].split(";");
- if (tokens.size() > 0) {
- if (tokens[0].strip_edges() == "*.*") {
- allow_other = true;
- } else {
- [allowed_types addObject:[NSString stringWithUTF8String:tokens[0].replace("*.", "").strip_edges().utf8().get_data()]];
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ int filter_slice_count = flt.get_slice_count(",");
+
+ NSMutableArray *type_filters = [[NSMutableArray alloc] init];
+ for (int j = 0; j < filter_slice_count; j++) {
+ String str = (flt.get_slice(",", j).strip_edges());
+ if (str.strip_edges() == "*.*" || str.strip_edges() == "*") {
+ allow_other = true;
+ } else if (!str.is_empty()) {
+ [type_filters addObject:[NSString stringWithUTF8String:str.replace("*.", "").strip_edges().utf8().get_data()]];
+ }
+ }
+
+ if ([type_filters count] > 0) {
+ NSString *name_str = [NSString stringWithUTF8String:((tokens.size() == 1) ? tokens[0] : vformat("%s (%s)", tokens[1], tokens[0])).strip_edges().utf8().get_data()];
+ [allowed_types addObject:type_filters];
+ [popup addItemWithTitle:name_str];
}
}
}
+ FileDialogDropdown *handler = [[FileDialogDropdown alloc] initWithDialog:p_panel fileTypes:allowed_types];
+ popup.target = handler;
+ popup.action = @selector(popupAction:);
- Callable callback = p_callback; // Make a copy for async completion handler.
- switch (p_mode) {
- case FILE_DIALOG_MODE_SAVE_FILE: {
- NSSavePanel *panel = [NSSavePanel savePanel];
+ [group addSubview:popup];
- [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
- if ([allowed_types count]) {
- [panel setAllowedFileTypes:allowed_types];
- }
- [panel setAllowsOtherFileTypes:allow_other];
- [panel setExtensionHidden:YES];
- [panel setCanSelectHiddenExtension:YES];
- [panel setCanCreateDirectories:YES];
- [panel setShowsHiddenFiles:p_show_hidden];
- if (p_filename != "") {
- NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
- [panel setNameFieldStringValue:fileurl];
- }
+ NSView *view = [[NSView alloc] initWithFrame:NSZeroRect];
+ view.translatesAutoresizingMaskIntoConstraints = NO;
+ [view addSubview:group];
+
+ NSMutableArray *constraints = [NSMutableArray array];
+ [constraints addObject:[popup.topAnchor constraintEqualToAnchor:group.topAnchor constant:10]];
+ [constraints addObject:[label.leadingAnchor constraintEqualToAnchor:group.leadingAnchor constant:10]];
+ [constraints addObject:[popup.leadingAnchor constraintEqualToAnchor:label.trailingAnchor constant:10]];
+ [constraints addObject:[popup.firstBaselineAnchor constraintEqualToAnchor:label.firstBaselineAnchor]];
+ [constraints addObject:[group.trailingAnchor constraintEqualToAnchor:popup.trailingAnchor constant:10]];
+ [constraints addObject:[group.bottomAnchor constraintEqualToAnchor:popup.bottomAnchor constant:10]];
+ [constraints addObject:[group.topAnchor constraintEqualToAnchor:view.topAnchor]];
+ [constraints addObject:[group.centerXAnchor constraintEqualToAnchor:view.centerXAnchor]];
+ [constraints addObject:[view.bottomAnchor constraintEqualToAnchor:group.bottomAnchor]];
+ [NSLayoutConstraint activateConstraints:constraints];
+
+ [p_panel setAllowsOtherFileTypes:allow_other];
+ if ([allowed_types count] > 0) {
+ [p_panel setAccessoryView:view];
+ [p_panel setAllowedFileTypes:[allowed_types objectAtIndex:0]];
+ }
+
+ return handler;
+}
+
+Error DisplayServerMacOS::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
+ _THREAD_SAFE_METHOD_
- [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
- completionHandler:^(NSInteger ret) {
- if (ret == NSModalResponseOK) {
- // Save bookmark for folder.
- if (OS::get_singleton()->is_sandboxed()) {
- NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
+ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
+ NSString *url = [NSString stringWithUTF8String:p_current_directory.utf8().get_data()];
+ FileDialogDropdown *handler = nullptr;
+
+ WindowID prev_focus = last_focused_window;
+
+ Callable callback = p_callback; // Make a copy for async completion handler.
+ if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) {
+ NSSavePanel *panel = [NSSavePanel savePanel];
+
+ [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
+ handler = _make_accessory_view(panel, p_filters);
+ [panel setExtensionHidden:YES];
+ [panel setCanSelectHiddenExtension:YES];
+ [panel setCanCreateDirectories:YES];
+ [panel setShowsHiddenFiles:p_show_hidden];
+ if (p_filename != "") {
+ NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
+ [panel setNameFieldStringValue:fileurl];
+ }
+
+ [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
+ completionHandler:^(NSInteger ret) {
+ if (ret == NSModalResponseOK) {
+ // Save bookmark for folder.
+ if (OS::get_singleton()->is_sandboxed()) {
+ NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
+ bool skip = false;
+ for (id bookmark in bookmarks) {
+ NSError *error = nil;
+ BOOL isStale = NO;
+ NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
+ if (!error && !isStale && ([[exurl path] compare:[[panel directoryURL] path]] == NSOrderedSame)) {
+ skip = true;
+ break;
+ }
+ }
+ if (!skip) {
+ NSError *error = nil;
+ NSData *bookmark = [[panel directoryURL] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
+ if (!error) {
+ NSArray *new_bookmarks = [bookmarks arrayByAddingObject:bookmark];
+ [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
+ }
+ }
+ }
+ // Callback.
+ Vector<String> files;
+ String url;
+ url.parse_utf8([[[panel URL] path] UTF8String]);
+ files.push_back(url);
+ if (!callback.is_null()) {
+ Variant v_status = true;
+ Variant v_files = files;
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
+ }
+ } else {
+ if (!callback.is_null()) {
+ Variant v_status = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
+ }
+ }
+ if (prev_focus != INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ }
+ }];
+ } else {
+ NSOpenPanel *panel = [NSOpenPanel openPanel];
+
+ [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
+ handler = _make_accessory_view(panel, p_filters);
+ [panel setExtensionHidden:YES];
+ [panel setCanSelectHiddenExtension:YES];
+ [panel setCanCreateDirectories:YES];
+ [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)];
+ [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)];
+ [panel setShowsHiddenFiles:p_show_hidden];
+ if (p_filename != "") {
+ NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
+ [panel setNameFieldStringValue:fileurl];
+ }
+ [panel setAllowsMultipleSelection:(p_mode == FILE_DIALOG_MODE_OPEN_FILES)];
+
+ [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
+ completionHandler:^(NSInteger ret) {
+ if (ret == NSModalResponseOK) {
+ // Save bookmark for folder.
+ NSArray *urls = [(NSOpenPanel *)panel URLs];
+ if (OS::get_singleton()->is_sandboxed()) {
+ NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
+ NSMutableArray *new_bookmarks = [bookmarks mutableCopy];
+ for (NSUInteger i = 0; i != [urls count]; ++i) {
bool skip = false;
for (id bookmark in bookmarks) {
NSError *error = nil;
BOOL isStale = NO;
NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
- if (!error && !isStale && ([[exurl path] compare:[[panel directoryURL] path]] == NSOrderedSame)) {
+ if (!error && !isStale && ([[exurl path] compare:[[urls objectAtIndex:i] path]] == NSOrderedSame)) {
skip = true;
break;
}
}
if (!skip) {
NSError *error = nil;
- NSData *bookmark = [[panel directoryURL] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
+ NSData *bookmark = [[urls objectAtIndex:i] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
if (!error) {
- NSArray *new_bookmarks = [bookmarks arrayByAddingObject:bookmark];
- [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
+ [new_bookmarks addObject:bookmark];
}
}
}
- // Callback.
- Vector<String> files;
+ [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
+ }
+ // Callback.
+ Vector<String> files;
+ for (NSUInteger i = 0; i != [urls count]; ++i) {
String url;
- url.parse_utf8([[[panel URL] path] UTF8String]);
+ url.parse_utf8([[[urls objectAtIndex:i] path] UTF8String]);
files.push_back(url);
- if (!callback.is_null()) {
- Variant v_status = true;
- Variant v_files = files;
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
- } else {
- if (!callback.is_null()) {
- Variant v_status = false;
- Variant v_files = Vector<String>();
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
}
- }];
- } break;
- case FILE_DIALOG_MODE_OPEN_ANY:
- case FILE_DIALOG_MODE_OPEN_FILE:
- case FILE_DIALOG_MODE_OPEN_FILES:
- case FILE_DIALOG_MODE_OPEN_DIR: {
- NSOpenPanel *panel = [NSOpenPanel openPanel];
-
- [panel setDirectoryURL:[NSURL fileURLWithPath:url]];
- if ([allowed_types count]) {
- [panel setAllowedFileTypes:allowed_types];
- }
- [panel setAllowsOtherFileTypes:allow_other];
- [panel setExtensionHidden:YES];
- [panel setCanSelectHiddenExtension:YES];
- [panel setCanCreateDirectories:YES];
- [panel setCanChooseFiles:(p_mode != FILE_DIALOG_MODE_OPEN_DIR)];
- [panel setCanChooseDirectories:(p_mode == FILE_DIALOG_MODE_OPEN_DIR || p_mode == FILE_DIALOG_MODE_OPEN_ANY)];
- [panel setShowsHiddenFiles:p_show_hidden];
- if (p_filename != "") {
- NSString *fileurl = [NSString stringWithUTF8String:p_filename.utf8().get_data()];
- [panel setNameFieldStringValue:fileurl];
- }
- [panel setAllowsMultipleSelection:(p_mode == FILE_DIALOG_MODE_OPEN_FILES)];
-
- [panel beginSheetModalForWindow:[[NSApplication sharedApplication] mainWindow]
- completionHandler:^(NSInteger ret) {
- if (ret == NSModalResponseOK) {
- // Save bookmark for folder.
- NSArray *urls = [(NSOpenPanel *)panel URLs];
- if (OS::get_singleton()->is_sandboxed()) {
- NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
- NSMutableArray *new_bookmarks = [bookmarks mutableCopy];
- for (NSUInteger i = 0; i != [urls count]; ++i) {
- bool skip = false;
- for (id bookmark in bookmarks) {
- NSError *error = nil;
- BOOL isStale = NO;
- NSURL *exurl = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
- if (!error && !isStale && ([[exurl path] compare:[[urls objectAtIndex:i] path]] == NSOrderedSame)) {
- skip = true;
- break;
- }
- }
- if (!skip) {
- NSError *error = nil;
- NSData *bookmark = [[urls objectAtIndex:i] bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
- if (!error) {
- [new_bookmarks addObject:bookmark];
- }
- }
- }
- [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
- }
- // Callback.
- Vector<String> files;
- for (NSUInteger i = 0; i != [urls count]; ++i) {
- String url;
- url.parse_utf8([[[urls objectAtIndex:i] path] UTF8String]);
- files.push_back(url);
- }
- if (!callback.is_null()) {
- Variant v_status = true;
- Variant v_files = files;
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
- } else {
- if (!callback.is_null()) {
- Variant v_status = false;
- Variant v_files = Vector<String>();
- Variant *v_args[2] = { &v_status, &v_files };
- Variant ret;
- Callable::CallError ce;
- callback.callp((const Variant **)&v_args, 2, ret, ce);
- }
+ if (!callback.is_null()) {
+ Variant v_status = true;
+ Variant v_files = files;
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
}
- }];
- } break;
+ } else {
+ if (!callback.is_null()) {
+ Variant v_status = false;
+ Variant v_files = Vector<String>();
+ Variant v_index = [handler getIndex];
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
+ Variant ret;
+ Callable::CallError ce;
+ callback.callp((const Variant **)&v_args, 3, ret, ce);
+ }
+ }
+ if (prev_focus != INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ }
+ }];
}
return OK;
@@ -2626,6 +2872,47 @@ void DisplayServerMacOS::window_set_title(const String &p_title, WindowID p_wind
[wd.window_object setTitle:[NSString stringWithUTF8String:p_title.utf8().get_data()]];
}
+Size2i DisplayServerMacOS::window_get_title_size(const String &p_title, WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ Size2i size;
+ ERR_FAIL_COND_V(!windows.has(p_window), size);
+
+ const WindowData &wd = windows[p_window];
+ if (wd.fullscreen || wd.borderless) {
+ return size;
+ }
+ if ([wd.window_object respondsToSelector:@selector(isMiniaturized)]) {
+ if ([wd.window_object isMiniaturized]) {
+ return size;
+ }
+ }
+
+ float scale = screen_get_max_scale();
+
+ if (wd.window_button_view) {
+ size.x = ([wd.window_button_view getOffset].x + [wd.window_button_view frame].size.width);
+ size.y = ([wd.window_button_view getOffset].y + [wd.window_button_view frame].size.height);
+ } else {
+ NSButton *cb = [wd.window_object standardWindowButton:NSWindowCloseButton];
+ NSButton *mb = [wd.window_object standardWindowButton:NSWindowMiniaturizeButton];
+ float cb_frame = NSMinX([cb frame]);
+ float mb_frame = NSMinX([mb frame]);
+ bool is_rtl = ([wd.window_object windowTitlebarLayoutDirection] == NSUserInterfaceLayoutDirectionRightToLeft);
+
+ float window_buttons_spacing = (is_rtl) ? (cb_frame - mb_frame) : (mb_frame - cb_frame);
+ size.x = window_buttons_spacing * 4;
+ size.y = [cb frame].origin.y + [cb frame].size.height;
+ }
+
+ NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont titleBarFontOfSize:0], NSFontAttributeName, nil];
+ NSSize text_size = [[[NSAttributedString alloc] initWithString:[NSString stringWithUTF8String:p_title.utf8().get_data()] attributes:attributes] size];
+ size.x += text_size.width;
+ size.y = MAX(size.y, text_size.height);
+
+ return size * scale;
+}
+
void DisplayServerMacOS::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -2868,6 +3155,15 @@ Size2i DisplayServerMacOS::window_get_max_size(WindowID p_window) const {
return wd.max_size;
}
+void DisplayServerMacOS::update_presentation_mode() {
+ for (const KeyValue<WindowID, WindowData> &wd : windows) {
+ if (wd.value.fullscreen && wd.value.exclusive_fullscreen) {
+ return;
+ }
+ }
+ [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+}
+
void DisplayServerMacOS::window_set_min_size(const Size2i p_size, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -2978,7 +3274,7 @@ void DisplayServerMacOS::window_set_mode(WindowMode p_mode, WindowID p_window) {
[wd.window_object toggleFullScreen:nil];
if (old_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
- [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+ update_presentation_mode();
}
wd.fullscreen = false;
@@ -3176,7 +3472,9 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win
} break;
case WINDOW_FLAG_BORDERLESS: {
// OrderOut prevents a lose focus bug with the window.
+ bool was_visible = false;
if ([wd.window_object isVisible]) {
+ was_visible = true;
[wd.window_object orderOut:nil];
}
wd.borderless = p_enabled;
@@ -3191,7 +3489,7 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win
[wd.window_object setFrame:frameRect display:NO];
}
_update_window_style(wd);
- if ([wd.window_object isVisible]) {
+ if (was_visible || [wd.window_object isVisible]) {
if ([wd.window_object isMiniaturized]) {
return;
} else if (wd.no_focus) {
@@ -4127,15 +4425,19 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
nsappname = [[NSProcessInfo processInfo] processName];
}
+ menu_delegate = [[GodotMenuDelegate alloc] init];
+
// Setup Dock menu.
dock_menu = [[NSMenu alloc] initWithTitle:@"_dock"];
[dock_menu setAutoenablesItems:NO];
+ [dock_menu setDelegate:menu_delegate];
// Setup Apple menu.
apple_menu = [[NSMenu alloc] initWithTitle:@""];
title = [NSString stringWithFormat:NSLocalizedString(@"About %@", nil), nsappname];
[apple_menu addItemWithTitle:title action:@selector(showAbout:) keyEquivalent:@""];
[apple_menu setAutoenablesItems:NO];
+ [apple_menu setDelegate:menu_delegate];
[apple_menu addItem:[NSMenuItem separatorItem]];
@@ -4165,8 +4467,6 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
[main_menu setSubmenu:apple_menu forItem:menu_item];
[main_menu setAutoenablesItems:NO];
- menu_delegate = [[GodotMenuDelegate alloc] init];
-
//!!!!!!!!!!!!!!!!!!!!!!!!!!
//TODO - do Vulkan and OpenGL support checks, driver selection and fallback
rendering_driver = p_rendering_driver;
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 639a7699ee..804028053d 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -1047,6 +1047,8 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
args.push_back("--p12-password");
args.push_back(certificate_pass);
}
+ args.push_back("--code-signature-flags");
+ args.push_back("runtime");
args.push_back("-v"); /* provide some more feedback */
@@ -1139,7 +1141,6 @@ Error EditorExportPlatformMacOS::_code_sign(const Ref<EditorExportPreset> &p_pre
Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPreset> &p_preset, const String &p_path,
const String &p_ent_path, bool p_should_error_on_non_code) {
-#ifdef MACOS_ENABLED
static Vector<String> extensions_to_sign;
if (extensions_to_sign.is_empty()) {
@@ -1186,7 +1187,6 @@ Error EditorExportPlatformMacOS::_code_sign_directory(const Ref<EditorExportPres
current_file = dir_access->get_next();
}
-#endif
return OK;
}
@@ -1225,7 +1225,7 @@ Error EditorExportPlatformMacOS::_copy_and_sign_files(Ref<DirAccess> &dir_access
if (extensions_to_sign.find(p_in_app_path.get_extension()) > -1) {
err = _code_sign(p_preset, p_in_app_path, p_ent_path, false);
}
- if (is_executable(p_in_app_path)) {
+ if (dir_access->file_exists(p_in_app_path) && is_executable(p_in_app_path)) {
// chmod with 0755 if the file is executable.
FileAccess::set_unix_permissions(p_in_app_path, 0755);
}
diff --git a/platform/macos/godot_menu_delegate.mm b/platform/macos/godot_menu_delegate.mm
index ebfe8b1f6d..1bfcfa7d9e 100644
--- a/platform/macos/godot_menu_delegate.mm
+++ b/platform/macos/godot_menu_delegate.mm
@@ -39,6 +39,34 @@
- (void)doNothing:(id)sender {
}
+- (void)menuNeedsUpdate:(NSMenu *)menu {
+ if (DisplayServer::get_singleton()) {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ ds->menu_open(menu);
+ }
+}
+
+- (void)menuDidClose:(NSMenu *)menu {
+ if (DisplayServer::get_singleton()) {
+ DisplayServerMacOS *ds = (DisplayServerMacOS *)DisplayServer::get_singleton();
+ ds->menu_close(menu);
+ }
+}
+
+- (void)menu:(NSMenu *)menu willHighlightItem:(NSMenuItem *)item {
+ if (item) {
+ GodotMenuItem *value = [item representedObject];
+ if (value && value->hover_callback != Callable()) {
+ // If custom callback is set, use it.
+ Variant tag = value->meta;
+ Variant *tagp = &tag;
+ Variant ret;
+ Callable::CallError ce;
+ value->hover_callback.callp((const Variant **)&tagp, 1, ret, ce);
+ }
+ }
+}
+
- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action {
NSString *ev_key = [[event charactersIgnoringModifiers] lowercaseString];
NSUInteger ev_modifiers = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h
index 8876ceae6a..b816ea1b3a 100644
--- a/platform/macos/godot_menu_item.h
+++ b/platform/macos/godot_menu_item.h
@@ -46,6 +46,7 @@ enum GlobalMenuCheckType {
@public
Callable callback;
Callable key_callback;
+ Callable hover_callback;
Variant meta;
GlobalMenuCheckType checkable_type;
int max_states;
diff --git a/platform/macos/godot_window_delegate.mm b/platform/macos/godot_window_delegate.mm
index 46355b4ae8..002ab0155f 100644
--- a/platform/macos/godot_window_delegate.mm
+++ b/platform/macos/godot_window_delegate.mm
@@ -157,7 +157,7 @@
DisplayServerMacOS::WindowData &wd = ds->get_window(window_id);
if (wd.exclusive_fullscreen) {
- [NSApp setPresentationOptions:NSApplicationPresentationDefault];
+ ds->update_presentation_mode();
}
wd.fullscreen = false;
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 1a1bba833d..ded80ba5f1 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -222,24 +222,45 @@ void DisplayServerWindows::tts_stop() {
Error DisplayServerWindows::file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback) {
_THREAD_SAFE_METHOD_
+ ERR_FAIL_INDEX_V(int(p_mode), FILE_DIALOG_MODE_SAVE_MAX, FAILED);
+
Vector<Char16String> filter_names;
Vector<Char16String> filter_exts;
for (const String &E : p_filters) {
Vector<String> tokens = E.split(";");
- if (tokens.size() == 2) {
- filter_exts.push_back(tokens[0].strip_edges().utf16());
- filter_names.push_back(tokens[1].strip_edges().utf16());
- } else if (tokens.size() == 1) {
- filter_exts.push_back(tokens[0].strip_edges().utf16());
- filter_names.push_back(tokens[0].strip_edges().utf16());
+ if (tokens.size() >= 1) {
+ String flt = tokens[0].strip_edges();
+ int filter_slice_count = flt.get_slice_count(",");
+ Vector<String> exts;
+ for (int j = 0; j < filter_slice_count; j++) {
+ String str = (flt.get_slice(",", j).strip_edges());
+ if (!str.is_empty()) {
+ exts.push_back(str);
+ }
+ }
+ if (!exts.is_empty()) {
+ String str = String(";").join(exts);
+ filter_exts.push_back(str.utf16());
+ if (tokens.size() == 2) {
+ filter_names.push_back(tokens[1].strip_edges().utf16());
+ } else {
+ filter_names.push_back(str.utf16());
+ }
+ }
}
}
+ if (filter_names.is_empty()) {
+ filter_exts.push_back(String("*.*").utf16());
+ filter_names.push_back(RTR("All Files").utf16());
+ }
Vector<COMDLG_FILTERSPEC> filters;
for (int i = 0; i < filter_names.size(); i++) {
filters.push_back({ (LPCWSTR)filter_names[i].ptr(), (LPCWSTR)filter_exts[i].ptr() });
}
+ WindowID prev_focus = last_focused_window;
+
HRESULT hr = S_OK;
IFileDialog *pfd = nullptr;
if (p_mode == FILE_DIALOG_MODE_SAVE_FILE) {
@@ -285,6 +306,9 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String
}
hr = pfd->Show(windows[window_id].hWnd);
+ UINT index = 0;
+ pfd->GetFileTypeIndex(&index);
+
if (SUCCEEDED(hr)) {
Vector<String> file_names;
@@ -324,22 +348,27 @@ Error DisplayServerWindows::file_dialog_show(const String &p_title, const String
if (!p_callback.is_null()) {
Variant v_status = true;
Variant v_files = file_names;
- Variant *v_args[2] = { &v_status, &v_files };
+ Variant v_index = index;
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
Variant ret;
Callable::CallError ce;
- p_callback.callp((const Variant **)&v_args, 2, ret, ce);
+ p_callback.callp((const Variant **)&v_args, 3, ret, ce);
}
} else {
if (!p_callback.is_null()) {
Variant v_status = false;
Variant v_files = Vector<String>();
- Variant *v_args[2] = { &v_status, &v_files };
+ Variant v_index = index;
+ Variant *v_args[3] = { &v_status, &v_files, &v_index };
Variant ret;
Callable::CallError ce;
- p_callback.callp((const Variant **)&v_args, 2, ret, ce);
+ p_callback.callp((const Variant **)&v_args, 3, ret, ce);
}
}
pfd->Release();
+ if (prev_focus != INVALID_WINDOW_ID) {
+ callable_mp(DisplayServer::get_singleton(), &DisplayServer::window_move_to_foreground).call_deferred(prev_focus);
+ }
return OK;
} else {
@@ -1191,6 +1220,51 @@ void DisplayServerWindows::window_set_title(const String &p_title, WindowID p_wi
SetWindowTextW(windows[p_window].hWnd, (LPCWSTR)(p_title.utf16().get_data()));
}
+Size2i DisplayServerWindows::window_get_title_size(const String &p_title, WindowID p_window) const {
+ _THREAD_SAFE_METHOD_
+
+ Size2i size;
+ ERR_FAIL_COND_V(!windows.has(p_window), size);
+
+ const WindowData &wd = windows[p_window];
+ if (wd.fullscreen || wd.minimized || wd.borderless) {
+ return size;
+ }
+
+ HDC hdc = GetDCEx(wd.hWnd, NULL, DCX_WINDOW);
+ if (hdc) {
+ Char16String s = p_title.utf16();
+ SIZE text_size;
+ if (GetTextExtentPoint32W(hdc, (LPCWSTR)(s.get_data()), s.length(), &text_size)) {
+ size.x = text_size.cx;
+ size.y = text_size.cy;
+ }
+
+ ReleaseDC(wd.hWnd, hdc);
+ }
+ RECT rect;
+ if (DwmGetWindowAttribute(wd.hWnd, DWMWA_CAPTION_BUTTON_BOUNDS, &rect, sizeof(RECT)) == S_OK) {
+ if (rect.right - rect.left > 0) {
+ ClientToScreen(wd.hWnd, (POINT *)&rect.left);
+ ClientToScreen(wd.hWnd, (POINT *)&rect.right);
+
+ if (win81p_PhysicalToLogicalPointForPerMonitorDPI) {
+ win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.left);
+ win81p_PhysicalToLogicalPointForPerMonitorDPI(0, (POINT *)&rect.right);
+ }
+
+ size.x += (rect.right - rect.left);
+ size.y = MAX(size.y, rect.bottom - rect.top);
+ }
+ }
+ if (icon.is_valid()) {
+ size.x += 32;
+ } else {
+ size.x += 16;
+ }
+ return size;
+}
+
void DisplayServerWindows::window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window) {
_THREAD_SAFE_METHOD_
@@ -4385,6 +4459,7 @@ bool DisplayServerWindows::winink_available = false;
GetPointerTypePtr DisplayServerWindows::win8p_GetPointerType = nullptr;
GetPointerPenInfoPtr DisplayServerWindows::win8p_GetPointerPenInfo = nullptr;
LogicalToPhysicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_LogicalToPhysicalPointForPerMonitorDPI = nullptr;
+PhysicalToLogicalPointForPerMonitorDPIPtr DisplayServerWindows::win81p_PhysicalToLogicalPointForPerMonitorDPI = nullptr;
typedef enum _SHC_PROCESS_DPI_AWARENESS {
SHC_PROCESS_DPI_UNAWARE = 0,
@@ -4522,6 +4597,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
win8p_GetPointerType = (GetPointerTypePtr)GetProcAddress(user32_lib, "GetPointerType");
win8p_GetPointerPenInfo = (GetPointerPenInfoPtr)GetProcAddress(user32_lib, "GetPointerPenInfo");
win81p_LogicalToPhysicalPointForPerMonitorDPI = (LogicalToPhysicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "LogicalToPhysicalPointForPerMonitorDPI");
+ win81p_PhysicalToLogicalPointForPerMonitorDPI = (PhysicalToLogicalPointForPerMonitorDPIPtr)GetProcAddress(user32_lib, "PhysicalToLogicalPointForPerMonitorDPI");
winink_available = win8p_GetPointerType && win8p_GetPointerPenInfo;
}
@@ -4579,9 +4655,22 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
// Init context and rendering device
#if defined(GLES3_ENABLED)
- if (rendering_driver == "opengl3") {
- int gl_version = detect_wgl_version();
- if (gl_version < 30003) {
+ bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_angle");
+ if (fallback && (rendering_driver == "opengl3")) {
+ Dictionary gl_info = detect_wgl();
+
+ bool force_angle = false;
+
+ Array device_list = GLOBAL_GET("rendering/gl_compatibility/force_angle_on_devices");
+ for (int i = 0; i < device_list.size(); i++) {
+ const Dictionary &device = device_list[i];
+ if (device.has("vendor") && device.has("name") && device["vendor"].operator String().to_upper() == gl_info["vendor"].operator String().to_upper() && (device["name"] == "*" || device["name"].operator String().to_upper() == gl_info["name"].operator String().to_upper())) {
+ force_angle = true;
+ break;
+ }
+ }
+
+ if (force_angle || (gl_info["version"].operator int() < 30003)) {
WARN_PRINT("Your video card drivers seem not to support the required OpenGL 3.3 version, switching to ANGLE.");
rendering_driver = "opengl3_angle";
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 28f2f7e6ff..48c8c20280 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -262,6 +262,7 @@ typedef struct tagPOINTER_PEN_INFO {
typedef BOOL(WINAPI *GetPointerTypePtr)(uint32_t p_id, POINTER_INPUT_TYPE *p_type);
typedef BOOL(WINAPI *GetPointerPenInfoPtr)(uint32_t p_id, POINTER_PEN_INFO *p_pen_info);
typedef BOOL(WINAPI *LogicalToPhysicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint);
+typedef BOOL(WINAPI *PhysicalToLogicalPointForPerMonitorDPIPtr)(HWND hwnd, LPPOINT lpPoint);
typedef struct {
BYTE bWidth; // Width, in pixels, of the image
@@ -309,6 +310,7 @@ class DisplayServerWindows : public DisplayServer {
// DPI conversion API
static LogicalToPhysicalPointForPerMonitorDPIPtr win81p_LogicalToPhysicalPointForPerMonitorDPI;
+ static PhysicalToLogicalPointForPerMonitorDPIPtr win81p_PhysicalToLogicalPointForPerMonitorDPI;
void _update_tablet_ctx(const String &p_old_driver, const String &p_new_driver);
String tablet_driver;
@@ -569,6 +571,7 @@ public:
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_title_size(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) const override;
virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID) override;
virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py
index b522a75a9c..51652fa814 100644
--- a/platform/windows/platform_windows_builders.py
+++ b/platform/windows/platform_windows_builders.py
@@ -5,14 +5,24 @@ All such functions are invoked in a subprocess on Windows to prevent build flaki
"""
import os
from detect import get_mingw_bin_prefix
+from detect import try_cmd
from platform_methods import subprocess_main
def make_debug_mingw(target, source, env):
mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"])
- os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
- os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0]))
- os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
+ if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
+ os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
+ else:
+ os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(target[0]))
+ if try_cmd("strip --version", env["mingw_prefix"], env["arch"]):
+ os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(target[0]))
+ else:
+ os.system("strip --strip-debug --strip-unneeded {0}".format(target[0]))
+ if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]):
+ os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
+ else:
+ os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(target[0]))
if __name__ == "__main__":
diff --git a/platform/windows/wgl_detect_version.cpp b/platform/windows/wgl_detect_version.cpp
index 264cd525c5..49da4b58c7 100644
--- a/platform/windows/wgl_detect_version.cpp
+++ b/platform/windows/wgl_detect_version.cpp
@@ -35,6 +35,7 @@
#include "core/string/print_string.h"
#include "core/string/ustring.h"
+#include "core/variant/dictionary.h"
#include <windows.h>
@@ -64,9 +65,11 @@ typedef HGLRC(APIENTRY *PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC, HGLRC, const int
typedef void *(APIENTRY *PFNWGLGETPROCADDRESS)(LPCSTR);
typedef const char *(APIENTRY *PFNWGLGETSTRINGPROC)(unsigned int);
-int detect_wgl_version() {
- int major = 0;
- int minor = 0;
+Dictionary detect_wgl() {
+ Dictionary gl_info;
+ gl_info["version"] = 0;
+ gl_info["vendor"] = String();
+ gl_info["name"] = String();
PFNWGLCREATECONTEXT gd_wglCreateContext;
PFNWGLMAKECURRENT gd_wglMakeCurrent;
@@ -75,14 +78,14 @@ int detect_wgl_version() {
HMODULE module = LoadLibraryW(L"opengl32.dll");
if (!module) {
- return 0;
+ return gl_info;
}
gd_wglCreateContext = (PFNWGLCREATECONTEXT)GetProcAddress(module, "wglCreateContext");
gd_wglMakeCurrent = (PFNWGLMAKECURRENT)GetProcAddress(module, "wglMakeCurrent");
gd_wglDeleteContext = (PFNWGLDELETECONTEXT)GetProcAddress(module, "wglDeleteContext");
gd_wglGetProcAddress = (PFNWGLGETPROCADDRESS)GetProcAddress(module, "wglGetProcAddress");
if (!gd_wglCreateContext || !gd_wglMakeCurrent || !gd_wglDeleteContext || !gd_wglGetProcAddress) {
- return 0;
+ return gl_info;
}
LPCWSTR class_name = L"EngineWGLDetect";
@@ -151,8 +154,8 @@ int detect_wgl_version() {
};
const char *version = (const char *)gd_wglGetString(WGL_VERSION);
if (version) {
- const String device_vendor = String::utf8((const char *)gd_wglGetString(WGL_VENDOR)).strip_edges();
- const String device_name = String::utf8((const char *)gd_wglGetString(WGL_RENDERER)).strip_edges();
+ const String device_vendor = String::utf8((const char *)gd_wglGetString(WGL_VENDOR)).strip_edges().trim_suffix(" Corporation");
+ const String device_name = String::utf8((const char *)gd_wglGetString(WGL_RENDERER)).strip_edges().trim_suffix("/PCIe/SSE2");
for (int i = 0; prefixes[i]; i++) {
size_t length = strlen(prefixes[i]);
if (strncmp(version, prefixes[i], length) == 0) {
@@ -160,12 +163,17 @@ int detect_wgl_version() {
break;
}
}
+ int major = 0;
+ int minor = 0;
#ifdef _MSC_VER
sscanf_s(version, "%d.%d", &major, &minor);
#else
sscanf(version, "%d.%d", &major, &minor);
#endif
print_verbose(vformat("Native OpenGL API detected: %d.%d: %s - %s", major, minor, device_vendor, device_name));
+ gl_info["vendor"] = device_vendor;
+ gl_info["name"] = device_name;
+ gl_info["version"] = major * 10000 + minor;
}
}
}
@@ -183,7 +191,7 @@ int detect_wgl_version() {
}
UnregisterClassW(class_name, hInstance);
- return major * 10000 + minor;
+ return gl_info;
}
#endif // WINDOWS_ENABLED && GLES3_ENABLED
diff --git a/platform/windows/wgl_detect_version.h b/platform/windows/wgl_detect_version.h
index 0be2923ba3..c110b1219e 100644
--- a/platform/windows/wgl_detect_version.h
+++ b/platform/windows/wgl_detect_version.h
@@ -33,7 +33,9 @@
#if defined(WINDOWS_ENABLED) && defined(GLES3_ENABLED)
-int detect_wgl_version();
+class Dictionary;
+
+Dictionary detect_wgl();
#endif // WINDOWS_ENABLED && GLES3_ENABLED
diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp
index 3ba81dba88..0a5f696992 100644
--- a/scene/2d/node_2d.cpp
+++ b/scene/2d/node_2d.cpp
@@ -424,14 +424,17 @@ Point2 Node2D::to_global(Point2 p_local) const {
}
void Node2D::_notification(int p_notification) {
- ERR_THREAD_GUARD;
switch (p_notification) {
case NOTIFICATION_ENTER_TREE: {
+ ERR_MAIN_THREAD_GUARD;
+
if (get_viewport()) {
get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty), CONNECT_REFERENCE_COUNTED);
}
} break;
case NOTIFICATION_EXIT_TREE: {
+ ERR_MAIN_THREAD_GUARD;
+
if (get_viewport()) {
get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty));
}
diff --git a/scene/2d/physics_body_2d.cpp b/scene/2d/physics_body_2d.cpp
index 19063981bf..fa94712396 100644
--- a/scene/2d/physics_body_2d.cpp
+++ b/scene/2d/physics_body_2d.cpp
@@ -447,9 +447,12 @@ void RigidBody2D::_body_state_changed(PhysicsDirectBodyState2D *p_state) {
lock_callback();
set_block_transform_notify(true); // don't want notify (would feedback loop)
- _sync_body_state(p_state);
- GDVIRTUAL_CALL(_integrate_forces, p_state);
+ if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
+ _sync_body_state(p_state);
+
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
+ }
_sync_body_state(p_state);
set_block_transform_notify(false); // want it back
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 07d84eebde..cb8279d534 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -131,10 +131,9 @@ void Node3D::_propagate_transform_changed(Node3D *p_origin) {
}
void Node3D::_notification(int p_what) {
- ERR_THREAD_GUARD;
-
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
+ ERR_MAIN_THREAD_GUARD;
ERR_FAIL_NULL(get_tree());
Node *p = get_parent();
@@ -163,6 +162,8 @@ void Node3D::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_TREE: {
+ ERR_MAIN_THREAD_GUARD;
+
notification(NOTIFICATION_EXIT_WORLD, true);
if (xform_change.in_list()) {
get_tree()->xform_change_list.remove(&xform_change);
@@ -176,6 +177,8 @@ void Node3D::_notification(int p_what) {
} break;
case NOTIFICATION_ENTER_WORLD: {
+ ERR_MAIN_THREAD_GUARD;
+
data.inside_world = true;
data.viewport = nullptr;
Node *parent = get_parent();
@@ -198,6 +201,8 @@ void Node3D::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_WORLD: {
+ ERR_MAIN_THREAD_GUARD;
+
#ifdef TOOLS_ENABLED
clear_gizmos();
#endif
@@ -211,6 +216,8 @@ void Node3D::_notification(int p_what) {
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
+ ERR_THREAD_GUARD;
+
#ifdef TOOLS_ENABLED
for (int i = 0; i < data.gizmos.size(); i++) {
data.gizmos.write[i]->transform();
diff --git a/scene/3d/physics_body_3d.cpp b/scene/3d/physics_body_3d.cpp
index 973d1cde58..a5f5ae6e61 100644
--- a/scene/3d/physics_body_3d.cpp
+++ b/scene/3d/physics_body_3d.cpp
@@ -502,9 +502,12 @@ void RigidBody3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
lock_callback();
set_ignore_transform_notification(true);
- _sync_body_state(p_state);
- GDVIRTUAL_CALL(_integrate_forces, p_state);
+ if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
+ _sync_body_state(p_state);
+
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
+ }
_sync_body_state(p_state);
set_ignore_transform_notification(false);
@@ -2935,9 +2938,12 @@ void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
}
set_ignore_transform_notification(true);
- _sync_body_state(p_state);
- GDVIRTUAL_CALL(_integrate_forces, p_state);
+ if (GDVIRTUAL_IS_OVERRIDDEN(_integrate_forces)) {
+ _sync_body_state(p_state);
+
+ GDVIRTUAL_CALL(_integrate_forces, p_state);
+ }
_sync_body_state(p_state);
set_ignore_transform_notification(false);
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index e7a2a26a29..20fcf9cba7 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -3223,7 +3223,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
int line_height = get_line_height();
if (GDVIRTUAL_IS_OVERRIDDEN(_filter_code_completion_candidates)) {
- code_completion_options.clear();
+ Vector<ScriptLanguage::CodeCompletionOption> code_completion_options_new;
code_completion_base = "";
/* Build options argument. */
@@ -3273,11 +3273,15 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
if (theme_cache.font.is_valid()) {
max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
}
- code_completion_options.push_back(option);
+ code_completion_options_new.push_back(option);
}
+ if (_should_reset_selected_option_for_new_options(code_completion_options_new)) {
+ code_completion_current_selected = 0;
+ }
+ code_completion_options = code_completion_options_new;
+
code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
- code_completion_current_selected = 0;
code_completion_force_item_center = -1;
code_completion_active = true;
queue_redraw();
@@ -3352,7 +3356,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
/* For now handle only traditional quoted strings. */
bool single_quote = in_string != -1 && first_quote_col > 0 && delimiters[in_string].start_key == "'";
- code_completion_options.clear();
+ Vector<ScriptLanguage::CodeCompletionOption> code_completion_options_new;
code_completion_base = string_to_complete;
/* Don't autocomplete setting numerical values. */
@@ -3384,7 +3388,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
if (string_to_complete.length() == 0) {
option.get_option_characteristics(string_to_complete);
- code_completion_options.push_back(option);
+ code_completion_options_new.push_back(option);
+
if (theme_cache.font.is_valid()) {
max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
}
@@ -3459,7 +3464,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
}
}
- code_completion_options.push_back(option);
+ code_completion_options_new.push_back(option);
if (theme_cache.font.is_valid()) {
max_width = MAX(max_width, theme_cache.font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size).width + offset);
}
@@ -3467,26 +3472,46 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
}
/* No options to complete, cancel. */
- if (code_completion_options.size() == 0) {
+ if (code_completion_options_new.size() == 0) {
cancel_code_completion();
return;
}
/* A perfect match, stop completion. */
- if (code_completion_options.size() == 1 && string_to_complete == code_completion_options[0].display) {
+ if (code_completion_options_new.size() == 1 && string_to_complete == code_completion_options_new[0].display) {
cancel_code_completion();
return;
}
- code_completion_options.sort_custom<CodeCompletionOptionCompare>();
+ code_completion_options_new.sort_custom<CodeCompletionOptionCompare>();
+ if (_should_reset_selected_option_for_new_options(code_completion_options_new)) {
+ code_completion_current_selected = 0;
+ }
+ code_completion_options = code_completion_options_new;
code_completion_longest_line = MIN(max_width, theme_cache.code_completion_max_width * theme_cache.font_size);
- code_completion_current_selected = 0;
code_completion_force_item_center = -1;
code_completion_active = true;
queue_redraw();
}
+// Assumes both the new_options and the code_completion_options are sorted.
+bool CodeEdit::_should_reset_selected_option_for_new_options(const Vector<ScriptLanguage::CodeCompletionOption> &p_new_options) {
+ if (code_completion_current_selected >= p_new_options.size()) {
+ return true;
+ }
+
+ for (int i = 0; i < code_completion_options.size() && i < p_new_options.size(); i++) {
+ if (i > code_completion_current_selected) {
+ return false;
+ }
+ if (code_completion_options[i].display != p_new_options[i].display) {
+ return true;
+ }
+ }
+ return false;
+}
+
void CodeEdit::_lines_edited_from(int p_from_line, int p_to_line) {
_update_delimiter_cache(p_from_line, p_to_line);
diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h
index 97c435b52d..4b0629d29d 100644
--- a/scene/gui/code_edit.h
+++ b/scene/gui/code_edit.h
@@ -222,6 +222,7 @@ private:
void _update_scroll_selected_line(float p_mouse_y);
void _filter_code_completion_candidates_impl();
+ bool _should_reset_selected_option_for_new_options(const Vector<ScriptLanguage::CodeCompletionOption> &p_new_options);
/* Line length guidelines */
TypedArray<int> line_length_guideline_columns;
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
index aff0ed6f06..957a8f276e 100644
--- a/scene/gui/dialogs.cpp
+++ b/scene/gui/dialogs.cpp
@@ -405,6 +405,7 @@ AcceptDialog::AcceptDialog() {
set_transient(true);
set_exclusive(true);
set_clamp_to_embedder(true);
+ set_keep_title_visible(true);
bg_panel = memnew(Panel);
add_child(bg_panel, false, INTERNAL_MODE_FRONT);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 3857281a66..b4649c2401 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -73,7 +73,7 @@ void FileDialog::set_visible(bool p_visible) {
}
}
-void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files) {
+void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter) {
if (p_ok) {
if (p_files.size() > 0) {
String f = p_files[0];
@@ -90,6 +90,7 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files) {
}
file->set_text(f);
dir->set_text(f.get_base_dir());
+ _filter_selected(p_filter);
}
} else {
file->set_text("");
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 1a87b79fdd..8ae84fc9dc 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -159,7 +159,7 @@ private:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
- void _native_dialog_cb(bool p_ok, const Vector<String> &p_files);
+ void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter);
bool _is_open_should_be_disabled();
diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp
index 13a42d0407..371d6c69af 100644
--- a/scene/gui/menu_bar.cpp
+++ b/scene/gui/menu_bar.cpp
@@ -202,52 +202,6 @@ void MenuBar::_popup_visibility_changed(bool p_visible) {
}
}
-void MenuBar::_update_submenu(const String &p_menu_name, PopupMenu *p_child) {
- int count = p_child->get_item_count();
- global_menus.insert(p_menu_name);
- for (int i = 0; i < count; i++) {
- if (p_child->is_item_separator(i)) {
- DisplayServer::get_singleton()->global_menu_add_separator(p_menu_name);
- } else if (!p_child->get_item_submenu(i).is_empty()) {
- Node *n = p_child->get_node_or_null(p_child->get_item_submenu(i));
- ERR_FAIL_NULL_MSG(n, "Item subnode does not exist: '" + p_child->get_item_submenu(i) + "'.");
- PopupMenu *pm = Object::cast_to<PopupMenu>(n);
- ERR_FAIL_NULL_MSG(pm, "Item subnode is not a PopupMenu: '" + p_child->get_item_submenu(i) + "'.");
-
- DisplayServer::get_singleton()->global_menu_add_submenu_item(p_menu_name, atr(p_child->get_item_text(i)), p_menu_name + "/" + itos(i));
- _update_submenu(p_menu_name + "/" + itos(i), pm);
- } else {
- int index = DisplayServer::get_singleton()->global_menu_add_item(p_menu_name, atr(p_child->get_item_text(i)), callable_mp(p_child, &PopupMenu::activate_item), Callable(), i);
-
- if (p_child->is_item_checkable(i)) {
- DisplayServer::get_singleton()->global_menu_set_item_checkable(p_menu_name, index, true);
- }
- if (p_child->is_item_radio_checkable(i)) {
- DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(p_menu_name, index, true);
- }
- DisplayServer::get_singleton()->global_menu_set_item_checked(p_menu_name, index, p_child->is_item_checked(i));
- DisplayServer::get_singleton()->global_menu_set_item_disabled(p_menu_name, index, p_child->is_item_disabled(i));
- DisplayServer::get_singleton()->global_menu_set_item_max_states(p_menu_name, index, p_child->get_item_max_states(i));
- DisplayServer::get_singleton()->global_menu_set_item_icon(p_menu_name, index, p_child->get_item_icon(i));
- DisplayServer::get_singleton()->global_menu_set_item_state(p_menu_name, index, p_child->get_item_state(i));
- DisplayServer::get_singleton()->global_menu_set_item_indentation_level(p_menu_name, index, p_child->get_item_indent(i));
- DisplayServer::get_singleton()->global_menu_set_item_tooltip(p_menu_name, index, p_child->get_item_tooltip(i));
- if (!p_child->is_item_shortcut_disabled(i) && p_child->get_item_shortcut(i).is_valid() && p_child->get_item_shortcut(i)->has_valid_event()) {
- Array events = p_child->get_item_shortcut(i)->get_events();
- for (int j = 0; j < events.size(); j++) {
- Ref<InputEventKey> ie = events[j];
- if (ie.is_valid()) {
- DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, index, ie->get_keycode_with_modifiers());
- break;
- }
- }
- } else if (p_child->get_item_accelerator(i) != Key::NONE) {
- DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, i, p_child->get_item_accelerator(i));
- }
- }
- }
-}
-
bool MenuBar::is_native_menu() const {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
@@ -258,52 +212,67 @@ bool MenuBar::is_native_menu() const {
return (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU) && is_native);
}
-void MenuBar::_clear_menu() {
+String MenuBar::bind_global_menu() {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return String();
+ }
+#endif
if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
- return;
+ return String();
}
- // Remove root menu items.
- int count = DisplayServer::get_singleton()->global_menu_get_item_count("_main");
- for (int i = count - 1; i >= 0; i--) {
- if (global_menus.has(DisplayServer::get_singleton()->global_menu_get_item_submenu("_main", i))) {
- DisplayServer::get_singleton()->global_menu_remove_item("_main", i);
+ if (!global_menu_name.is_empty()) {
+ return global_menu_name; // Already bound.
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ global_menu_name = "__MenuBar#" + itos(get_instance_id());
+
+ int global_start_idx = -1;
+ int count = ds->global_menu_get_item_count("_main");
+ String prev_tag;
+ for (int i = 0; i < count; i++) {
+ String tag = ds->global_menu_get_item_tag("_main", i).operator String().get_slice("#", 1);
+ if (!tag.is_empty() && tag != prev_tag) {
+ if (i >= start_index) {
+ global_start_idx = i;
+ break;
+ }
}
+ prev_tag = tag;
}
- // Erase submenu contents.
- for (const String &E : global_menus) {
- DisplayServer::get_singleton()->global_menu_clear(E);
+ if (global_start_idx == -1) {
+ global_start_idx = count;
}
- global_menus.clear();
-}
-void MenuBar::_update_menu() {
- _clear_menu();
+ Vector<PopupMenu *> popups = _get_popups();
+ for (int i = 0; i < menu_cache.size(); i++) {
+ String submenu_name = popups[i]->bind_global_menu();
+ int index = ds->global_menu_add_submenu_item("_main", menu_cache[i].name, submenu_name, global_start_idx + i);
+ ds->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(i));
+ ds->global_menu_set_item_hidden("_main", index, menu_cache[i].hidden);
+ ds->global_menu_set_item_disabled("_main", index, menu_cache[i].disabled);
+ ds->global_menu_set_item_tooltip("_main", index, menu_cache[i].tooltip);
+ }
+
+ return global_menu_name;
+}
- if (!is_visible_in_tree()) {
+void MenuBar::unbind_global_menu() {
+ if (global_menu_name.is_empty()) {
return;
}
- int index = start_index;
- if (is_native_menu()) {
- Vector<PopupMenu *> popups = _get_popups();
- String root_name = "MenuBar<" + String::num_int64((uint64_t)this, 16) + ">";
- for (int i = 0; i < popups.size(); i++) {
- if (menu_cache[i].hidden) {
- continue;
- }
- String menu_name = atr(String(popups[i]->get_meta("_menu_name", popups[i]->get_name())));
-
- index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", menu_name, root_name + "/" + itos(i), index);
- if (menu_cache[i].disabled) {
- DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", index, true);
- }
- _update_submenu(root_name + "/" + itos(i), popups[i]);
- index++;
- }
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int global_start = _find_global_start_index();
+ Vector<PopupMenu *> popups = _get_popups();
+ for (int i = menu_cache.size() - 1; i >= 0; i--) {
+ popups[i]->unbind_global_menu();
+ ds->global_menu_remove_item("_main", global_start + i);
}
- update_minimum_size();
- queue_redraw();
+
+ global_menu_name = String();
}
void MenuBar::_notification(int p_what) {
@@ -312,25 +281,43 @@ void MenuBar::_notification(int p_what) {
if (get_menu_count() > 0) {
_refresh_menu_names();
}
+ if (is_native_menu()) {
+ bind_global_menu();
+ }
} break;
case NOTIFICATION_EXIT_TREE: {
- _clear_menu();
+ unbind_global_menu();
} break;
case NOTIFICATION_MOUSE_EXIT: {
focused_menu = -1;
selected_menu = -1;
queue_redraw();
} break;
- case NOTIFICATION_TRANSLATION_CHANGED:
+ case NOTIFICATION_TRANSLATION_CHANGED: {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+ int global_start = _find_global_start_index();
+ for (int i = 0; i < menu_cache.size(); i++) {
+ shape(menu_cache.write[i]);
+ if (is_global) {
+ ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name));
+ }
+ }
+ } break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
for (int i = 0; i < menu_cache.size(); i++) {
shape(menu_cache.write[i]);
}
- _update_menu();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
- _update_menu();
+ if (is_native_menu()) {
+ if (is_visible_in_tree()) {
+ bind_global_menu();
+ } else {
+ unbind_global_menu();
+ }
+ }
} break;
case NOTIFICATION_DRAW: {
if (is_native_menu()) {
@@ -512,14 +499,20 @@ void MenuBar::shape(Menu &p_menu) {
}
void MenuBar::_refresh_menu_names() {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+ int global_start = _find_global_start_index();
+
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) {
menu_cache.write[i].name = popups[i]->get_name();
shape(menu_cache.write[i]);
+ if (is_global) {
+ ds->global_menu_set_item_text("_main", global_start + i, atr(menu_cache[i].name));
+ }
}
}
- _update_menu();
}
Vector<PopupMenu *> MenuBar::_get_popups() const {
@@ -560,11 +553,14 @@ void MenuBar::add_child_notify(Node *p_child) {
menu_cache.push_back(menu);
p_child->connect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
- p_child->connect("menu_changed", callable_mp(this, &MenuBar::_update_menu));
p_child->connect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(true));
p_child->connect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(false));
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ String submenu_name = pm->bind_global_menu();
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, _find_global_start_index() + menu_cache.size() - 1);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(menu_cache.size() - 1));
+ }
}
void MenuBar::move_child_notify(Node *p_child) {
@@ -586,9 +582,20 @@ void MenuBar::move_child_notify(Node *p_child) {
}
Menu menu = menu_cache[old_idx];
menu_cache.remove_at(old_idx);
- menu_cache.insert(get_menu_idx_from_control(pm), menu);
+ int new_idx = get_menu_idx_from_control(pm);
+ menu_cache.insert(new_idx, menu);
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ int global_start = _find_global_start_index();
+ if (old_idx != -1) {
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", global_start + old_idx);
+ }
+ if (new_idx != -1) {
+ String submenu_name = pm->bind_global_menu();
+ int index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", atr(menu.name), submenu_name, global_start + new_idx);
+ DisplayServer::get_singleton()->global_menu_set_item_tag("_main", index, global_menu_name + "#" + itos(new_idx));
+ }
+ }
}
void MenuBar::remove_child_notify(Node *p_child) {
@@ -603,15 +610,17 @@ void MenuBar::remove_child_notify(Node *p_child) {
menu_cache.remove_at(idx);
+ if (!global_menu_name.is_empty()) {
+ pm->unbind_global_menu();
+ DisplayServer::get_singleton()->global_menu_remove_item("_main", _find_global_start_index() + idx);
+ }
+
p_child->remove_meta("_menu_name");
p_child->remove_meta("_menu_tooltip");
p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
- p_child->disconnect("menu_changed", callable_mp(this, &MenuBar::_update_menu));
p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed));
p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed));
-
- _update_menu();
}
void MenuBar::_bind_methods() {
@@ -699,7 +708,8 @@ void MenuBar::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
- _update_menu();
+ update_minimum_size();
+ queue_redraw();
}
}
@@ -710,7 +720,8 @@ Control::TextDirection MenuBar::get_text_direction() const {
void MenuBar::set_language(const String &p_language) {
if (language != p_language) {
language = p_language;
- _update_menu();
+ update_minimum_size();
+ queue_redraw();
}
}
@@ -732,7 +743,10 @@ bool MenuBar::is_flat() const {
void MenuBar::set_start_index(int p_index) {
if (start_index != p_index) {
start_index = p_index;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ unbind_global_menu();
+ bind_global_menu();
+ }
}
}
@@ -742,11 +756,12 @@ int MenuBar::get_start_index() const {
void MenuBar::set_prefer_global_menu(bool p_enabled) {
if (is_native != p_enabled) {
+ is_native = p_enabled;
if (is_native) {
- _clear_menu();
+ bind_global_menu();
+ } else {
+ unbind_global_menu();
}
- is_native = p_enabled;
- _update_menu();
}
}
@@ -790,7 +805,9 @@ void MenuBar::set_menu_title(int p_menu, const String &p_title) {
}
menu_cache.write[p_menu].name = p_title;
shape(menu_cache.write[p_menu]);
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_text("_main", _find_global_start_index() + p_menu, atr(menu_cache[p_menu].name));
+ }
}
String MenuBar::get_menu_title(int p_menu) const {
@@ -802,7 +819,10 @@ void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
PopupMenu *pm = get_menu_popup(p_menu);
pm->set_meta("_menu_tooltip", p_tooltip);
- menu_cache.write[p_menu].name = p_tooltip;
+ menu_cache.write[p_menu].tooltip = p_tooltip;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tooltip("_main", _find_global_start_index() + p_menu, p_tooltip);
+ }
}
String MenuBar::get_menu_tooltip(int p_menu) const {
@@ -813,7 +833,9 @@ String MenuBar::get_menu_tooltip(int p_menu) const {
void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].disabled = p_disabled;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", _find_global_start_index() + p_menu, p_disabled);
+ }
}
bool MenuBar::is_menu_disabled(int p_menu) const {
@@ -824,7 +846,9 @@ bool MenuBar::is_menu_disabled(int p_menu) const {
void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].hidden = p_hidden;
- _update_menu();
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_hidden("_main", _find_global_start_index() + p_menu, p_hidden);
+ }
}
bool MenuBar::is_menu_hidden(int p_menu) const {
diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h
index 4d6e76d9b6..ba4df5f229 100644
--- a/scene/gui/menu_bar.h
+++ b/scene/gui/menu_bar.h
@@ -66,7 +66,6 @@ class MenuBar : public Control {
}
};
Vector<Menu> menu_cache;
- HashSet<String> global_menus;
int focused_menu = -1;
int selected_menu = -1;
@@ -114,9 +113,23 @@ class MenuBar : public Control {
void _open_popup(int p_index, bool p_focus_item = false);
void _popup_visibility_changed(bool p_visible);
- void _update_submenu(const String &p_menu_name, PopupMenu *p_child);
- void _clear_menu();
- void _update_menu();
+
+ String global_menu_name;
+
+ int _find_global_start_index() {
+ if (global_menu_name.is_empty()) {
+ return -1;
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int count = ds->global_menu_get_item_count("_main");
+ for (int i = 0; i < count; i++) {
+ if (ds->global_menu_get_item_tag("_main", i).operator String().begins_with(global_menu_name)) {
+ return i;
+ }
+ }
+ return -1;
+ }
protected:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
@@ -130,6 +143,9 @@ protected:
public:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
+ String bind_global_menu();
+ void unbind_global_menu();
+
void set_switch_on_hover(bool p_enabled);
bool is_switch_on_hover();
void set_disable_shortcuts(bool p_disabled);
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 54fd8b8745..e3b0a18325 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -40,6 +40,86 @@
#include "scene/gui/menu_bar.h"
#include "scene/theme/theme_db.h"
+String PopupMenu::bind_global_menu() {
+#ifdef TOOLS_ENABLED
+ if (is_part_of_edited_scene()) {
+ return String();
+ }
+#endif
+ if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) {
+ return String();
+ }
+
+ if (!global_menu_name.is_empty()) {
+ return global_menu_name; // Already bound;
+ }
+
+ DisplayServer *ds = DisplayServer::get_singleton();
+ global_menu_name = "__PopupMenu#" + itos(get_instance_id());
+ ds->global_menu_set_popup_callbacks(global_menu_name, callable_mp(this, &PopupMenu::_about_to_popup), callable_mp(this, &PopupMenu::_about_to_close));
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (item.separator) {
+ ds->global_menu_add_separator(global_menu_name);
+ } else {
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), i);
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name);
+ item.submenu_bound = true;
+ }
+ }
+ if (item.checkable_type == Item::CHECKABLE_TYPE_CHECK_BOX) {
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ } else if (item.checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON) {
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+ ds->global_menu_set_item_checked(global_menu_name, index, item.checked);
+ ds->global_menu_set_item_disabled(global_menu_name, index, item.disabled);
+ ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states);
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_state(global_menu_name, index, item.state);
+ ds->global_menu_set_item_indentation_level(global_menu_name, index, item.indent);
+ ds->global_menu_set_item_tooltip(global_menu_name, index, item.tooltip);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ } else if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ }
+ }
+ return global_menu_name;
+}
+
+void PopupMenu::unbind_global_menu() {
+ if (global_menu_name.is_empty()) {
+ return;
+ }
+
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ pm->unbind_global_menu();
+ }
+ item.submenu_bound = false;
+ }
+ }
+ DisplayServer::get_singleton()->global_menu_clear(global_menu_name);
+
+ global_menu_name = String();
+}
+
String PopupMenu::_get_accel_text(const Item &p_item) const {
if (p_item.shortcut.is_valid()) {
return p_item.shortcut->get_as_text();
@@ -821,11 +901,17 @@ void PopupMenu::_menu_changed() {
void PopupMenu::add_child_notify(Node *p_child) {
Window::add_child_notify(p_child);
- PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
- if (!pm) {
- return;
+ if (Object::cast_to<PopupMenu>(p_child) && !global_menu_name.is_empty()) {
+ String node_name = p_child->get_name();
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(node_name));
+ for (int i = 0; i < items.size(); i++) {
+ if (items[i].submenu == node_name) {
+ String submenu_name = pm->bind_global_menu();
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, submenu_name);
+ items.write[i].submenu_bound = true;
+ }
+ }
}
- p_child->connect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
_menu_changed();
}
@@ -836,7 +922,16 @@ void PopupMenu::remove_child_notify(Node *p_child) {
if (!pm) {
return;
}
- p_child->disconnect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed));
+ if (Object::cast_to<PopupMenu>(p_child) && !global_menu_name.is_empty()) {
+ String node_name = p_child->get_name();
+ for (int i = 0; i < items.size(); i++) {
+ if (items[i].submenu == node_name) {
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, i, String());
+ items.write[i].submenu_bound = false;
+ }
+ }
+ pm->unbind_global_menu();
+ }
_menu_changed();
}
@@ -857,9 +952,15 @@ void PopupMenu::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED:
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
for (int i = 0; i < items.size(); i++) {
- items.write[i].xl_text = atr(items[i].text);
- items.write[i].dirty = true;
+ Item &item = items.write[i];
+ item.xl_text = atr(item.text);
+ item.dirty = true;
+ if (is_global) {
+ ds->global_menu_set_item_text(global_menu_name, i, item.xl_text);
+ }
_shape_item(i);
}
@@ -1031,6 +1132,14 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) {
ITEM_SETUP_WITH_ACCEL(p_label, p_id, p_accel);
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
@@ -1045,6 +1154,15 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe
item.icon = p_icon;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
@@ -1059,10 +1177,20 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) {
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1073,10 +1201,22 @@ void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
+ _menu_changed();
}
void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_accel) {
@@ -1085,10 +1225,20 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1099,10 +1249,21 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1113,11 +1274,22 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
item.state = p_default_state;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (item.accel != Key::NONE) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, item.accel);
+ }
+ ds->global_menu_set_item_max_states(global_menu_name, index, item.max_states);
+ ds->global_menu_set_item_state(global_menu_name, index, item.state);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
_menu_changed();
+ notify_property_list_changed();
}
#define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo) \
@@ -1135,10 +1307,26 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g
ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global, p_allow_echo);
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1148,10 +1336,27 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc
item.icon = p_icon;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1161,10 +1366,27 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1175,10 +1397,28 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<
item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1188,10 +1428,27 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1202,10 +1459,28 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons
item.checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ if (!item.shortcut_is_disabled && item.shortcut.is_valid() && item.shortcut->has_valid_event()) {
+ Array events = item.shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, index, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ ds->global_menu_set_item_icon(global_menu_name, index, item.icon);
+ ds->global_menu_set_item_radio_checkable(global_menu_name, index, true);
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1217,10 +1492,22 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu,
item.submenu = p_submenu;
items.push_back(item);
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ int index = ds->global_menu_add_item(global_menu_name, item.xl_text, callable_mp(this, &PopupMenu::activate_item), Callable(), items.size() - 1);
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu)); // Find first menu with this name.
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ ds->global_menu_set_item_submenu(global_menu_name, index, submenu_name);
+ items.write[index].submenu_bound = true;
+ }
+ }
+
_shape_item(items.size() - 1);
control->queue_redraw();
child_controls_changed();
+ notify_property_list_changed();
_menu_changed();
}
@@ -1240,6 +1527,10 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) {
items.write[p_idx].text = p_text;
items.write[p_idx].xl_text = atr(p_text);
items.write[p_idx].dirty = true;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_text(global_menu_name, p_idx, items[p_idx].xl_text);
+ }
_shape_item(p_idx);
control->queue_redraw();
@@ -1284,6 +1575,10 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
items.write[p_idx].icon = p_icon;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_icon(global_menu_name, p_idx, items[p_idx].icon);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1332,6 +1627,10 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) {
items.write[p_idx].checked = p_checked;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, p_checked);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1349,6 +1648,10 @@ void PopupMenu::set_item_id(int p_idx, int p_id) {
items.write[p_idx].id = p_id;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tag(global_menu_name, p_idx, p_id);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1367,6 +1670,10 @@ void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) {
items.write[p_idx].accel = p_accel;
items.write[p_idx].dirty = true;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_accelerator(global_menu_name, p_idx, p_accel);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1383,7 +1690,6 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) {
}
items.write[p_idx].metadata = p_meta;
- control->queue_redraw();
child_controls_changed();
_menu_changed();
}
@@ -1399,6 +1705,11 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) {
}
items.write[p_idx].disabled = p_disabled;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_disabled(global_menu_name, p_idx, p_disabled);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1414,7 +1725,30 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
return;
}
+ if (!global_menu_name.is_empty()) {
+ if (items[p_idx].submenu_bound) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(items[p_idx].submenu));
+ if (pm) {
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, String());
+ pm->unbind_global_menu();
+ }
+ items.write[p_idx].submenu_bound = false;
+ }
+ }
+
items.write[p_idx].submenu = p_submenu;
+
+ if (!global_menu_name.is_empty()) {
+ if (!items[p_idx].submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(items[p_idx].submenu));
+ if (pm) {
+ String submenu_name = pm->bind_global_menu();
+ DisplayServer::get_singleton()->global_menu_set_item_submenu(global_menu_name, p_idx, submenu_name);
+ items.write[p_idx].submenu_bound = true;
+ }
+ }
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1423,6 +1757,11 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) {
void PopupMenu::toggle_item_checked(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
items.write[p_idx].checked = !items[p_idx].checked;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checked(global_menu_name, p_idx, items[p_idx].checked);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1569,6 +1908,11 @@ void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
}
items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_checkable(global_menu_name, p_idx, p_checkable);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1585,6 +1929,11 @@ void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
}
items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(global_menu_name, p_idx, p_radio_checkable);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1600,6 +1949,11 @@ void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) {
}
items.write[p_idx].tooltip = p_tooltip;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_tooltip(global_menu_name, p_idx, p_tooltip);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1625,6 +1979,21 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo
_ref_shortcut(items[p_idx].shortcut);
}
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE);
+ if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) {
+ Array events = items[p_idx].shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1640,6 +2009,10 @@ void PopupMenu::set_item_indent(int p_idx, int p_indent) {
}
items.write[p_idx].indent = p_indent;
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_indentation_level(global_menu_name, p_idx, p_indent);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1656,6 +2029,11 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) {
}
items.write[p_idx].state = p_state;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, p_state);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1671,6 +2049,22 @@ void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) {
}
items.write[p_idx].shortcut_is_disabled = p_disabled;
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer *ds = DisplayServer::get_singleton();
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, Key::NONE);
+ if (!items[p_idx].shortcut_is_disabled && items[p_idx].shortcut.is_valid() && items[p_idx].shortcut->has_valid_event()) {
+ Array events = items[p_idx].shortcut->get_events();
+ for (int j = 0; j < events.size(); j++) {
+ Ref<InputEventKey> ie = events[j];
+ if (ie.is_valid()) {
+ ds->global_menu_set_item_accelerator(global_menu_name, p_idx, ie->get_keycode_with_modifiers());
+ break;
+ }
+ }
+ }
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1686,6 +2080,10 @@ void PopupMenu::toggle_item_multistate(int p_idx) {
items.write[p_idx].state = 0;
}
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_set_item_state(global_menu_name, p_idx, items[p_idx].state);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1739,11 +2137,23 @@ void PopupMenu::set_item_count(int p_count) {
return;
}
+ DisplayServer *ds = DisplayServer::get_singleton();
+ bool is_global = !global_menu_name.is_empty();
+
+ if (is_global && prev_size > p_count) {
+ for (int i = prev_size - 1; i >= p_count; i--) {
+ ds->global_menu_remove_item(global_menu_name, i);
+ }
+ }
+
items.resize(p_count);
if (prev_size < p_count) {
for (int i = prev_size; i < p_count; i++) {
items.write[i].id = i;
+ if (is_global) {
+ ds->global_menu_add_item(global_menu_name, String(), callable_mp(this, &PopupMenu::activate_item), Callable(), i);
+ }
}
}
@@ -1828,6 +2238,16 @@ bool PopupMenu::activate_item_by_event(const Ref<InputEvent> &p_event, bool p_fo
return false;
}
+void PopupMenu::_about_to_popup() {
+ ERR_MAIN_THREAD_GUARD;
+ emit_signal(SNAME("about_to_popup"));
+}
+
+void PopupMenu::_about_to_close() {
+ ERR_MAIN_THREAD_GUARD;
+ emit_signal(SNAME("popup_hide"));
+}
+
void PopupMenu::activate_item(int p_idx) {
ERR_FAIL_INDEX(p_idx, items.size());
ERR_FAIL_COND(items[p_idx].separator);
@@ -1890,6 +2310,11 @@ void PopupMenu::remove_item(int p_idx) {
}
items.remove_at(p_idx);
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_remove_item(global_menu_name, p_idx);
+ }
+
control->queue_redraw();
child_controls_changed();
_menu_changed();
@@ -1904,6 +2329,11 @@ void PopupMenu::add_separator(const String &p_text, int p_id) {
sep.xl_text = atr(p_text);
}
items.push_back(sep);
+
+ if (!global_menu_name.is_empty()) {
+ DisplayServer::get_singleton()->global_menu_add_separator(global_menu_name);
+ }
+
control->queue_redraw();
_menu_changed();
}
@@ -1922,7 +2352,22 @@ void PopupMenu::clear(bool p_free_submenus) {
}
}
}
+
+ if (!global_menu_name.is_empty()) {
+ for (int i = 0; i < items.size(); i++) {
+ Item &item = items.write[i];
+ if (!item.submenu.is_empty()) {
+ PopupMenu *pm = Object::cast_to<PopupMenu>(get_node_or_null(item.submenu));
+ if (pm) {
+ pm->unbind_global_menu();
+ }
+ item.submenu_bound = false;
+ }
+ }
+ DisplayServer::get_singleton()->global_menu_clear(global_menu_name);
+ }
items.clear();
+
mouse_over = -1;
control->queue_redraw();
child_controls_changed();
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index f123d08400..5d5f4a8322 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -75,6 +75,7 @@ class PopupMenu : public Popup {
bool shortcut_is_global = false;
bool shortcut_is_disabled = false;
bool allow_echo = false;
+ bool submenu_bound = false;
// Returns (0,0) if icon is null.
Size2 get_icon_size() const {
@@ -88,6 +89,8 @@ class PopupMenu : public Popup {
}
};
+ String global_menu_name;
+
bool close_allowed = false;
bool activated_by_keyboard = false;
@@ -213,6 +216,9 @@ public:
virtual void _parent_focused() override;
+ String bind_global_menu();
+ void unbind_global_menu();
+
void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
@@ -293,6 +299,9 @@ public:
bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false);
void activate_item(int p_idx);
+ void _about_to_popup();
+ void _about_to_close();
+
void remove_item(int p_idx);
void add_separator(const String &p_text = String(), int p_id = -1);
diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp
index 851a94b32f..0d33774e20 100644
--- a/scene/gui/subviewport_container.cpp
+++ b/scene/gui/subviewport_container.cpp
@@ -190,6 +190,13 @@ void SubViewportContainer::_propagate_nonpositional_event(const Ref<InputEvent>
return;
}
+ bool send;
+ if (GDVIRTUAL_CALL(_propagate_input_event, p_event, send)) {
+ if (!send) {
+ return;
+ }
+ }
+
_send_event_to_viewports(p_event);
}
@@ -204,6 +211,13 @@ void SubViewportContainer::gui_input(const Ref<InputEvent> &p_event) {
return;
}
+ bool send;
+ if (GDVIRTUAL_CALL(_propagate_input_event, p_event, send)) {
+ if (!send) {
+ return;
+ }
+ }
+
if (stretch && shrink > 1) {
Transform2D xform;
xform.scale(Vector2(1, 1) / shrink);
@@ -275,6 +289,8 @@ void SubViewportContainer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch"), "set_stretch", "is_stretch_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_shrink"), "set_stretch_shrink", "get_stretch_shrink");
+
+ GDVIRTUAL_BIND(_propagate_input_event, "event");
}
SubViewportContainer::SubViewportContainer() {
diff --git a/scene/gui/subviewport_container.h b/scene/gui/subviewport_container.h
index 3c6cd09d66..06420de730 100644
--- a/scene/gui/subviewport_container.h
+++ b/scene/gui/subviewport_container.h
@@ -50,6 +50,8 @@ protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
+ GDVIRTUAL1RC(bool, _propagate_input_event, Ref<InputEvent>);
+
public:
void set_stretch(bool p_enable);
bool is_stretch_enabled() const;
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 06acdd0237..8f38a6f6c8 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -273,10 +273,9 @@ void CanvasItem::_exit_canvas() {
}
void CanvasItem::_notification(int p_what) {
- ERR_MAIN_THREAD_GUARD;
-
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
+ ERR_MAIN_THREAD_GUARD;
ERR_FAIL_COND(!is_inside_tree());
Node *parent = get_parent();
@@ -336,6 +335,8 @@ void CanvasItem::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_TREE: {
+ ERR_MAIN_THREAD_GUARD;
+
if (xform_change.in_list()) {
get_tree()->xform_change_list.remove(&xform_change);
}
@@ -357,14 +358,20 @@ void CanvasItem::_notification(int p_what) {
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
+ ERR_MAIN_THREAD_GUARD;
+
emit_signal(SceneStringNames::get_singleton()->visibility_changed);
} break;
case NOTIFICATION_WORLD_2D_CHANGED: {
+ ERR_MAIN_THREAD_GUARD;
+
_exit_canvas();
_enter_canvas();
} break;
case NOTIFICATION_PARENTED: {
// The node is not inside the tree during this notification.
+ ERR_MAIN_THREAD_GUARD;
+
_notify_transform();
} break;
}
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index f5468b29c5..71ec9b29c5 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -270,20 +270,21 @@ void Window::set_title(const String &p_title) {
ERR_MAIN_THREAD_GUARD;
title = p_title;
+ tr_title = atr(p_title);
+#ifdef DEBUG_ENABLED
+ if (window_id == DisplayServer::MAIN_WINDOW_ID) {
+ // Append a suffix to the window title to denote that the project is running
+ // from a debug build (including the editor). Since this results in lower performance,
+ // this should be clearly presented to the user.
+ tr_title = vformat("%s (DEBUG)", tr_title);
+ }
+#endif
if (embedder) {
embedder->_sub_window_update(this);
} else if (window_id != DisplayServer::INVALID_WINDOW_ID) {
- String tr_title = atr(p_title);
-#ifdef DEBUG_ENABLED
- if (window_id == DisplayServer::MAIN_WINDOW_ID) {
- // Append a suffix to the window title to denote that the project is running
- // from a debug build (including the editor). Since this results in lower performance,
- // this should be clearly presented to the user.
- tr_title = vformat("%s (DEBUG)", tr_title);
- }
-#endif
DisplayServer::get_singleton()->window_set_title(tr_title, window_id);
+ _update_window_size();
}
}
@@ -586,15 +587,6 @@ void Window::_make_window() {
DisplayServer::get_singleton()->window_set_max_size(Size2i(), window_id);
DisplayServer::get_singleton()->window_set_min_size(Size2i(), window_id);
DisplayServer::get_singleton()->window_set_mouse_passthrough(mpath, window_id);
- String tr_title = atr(title);
-#ifdef DEBUG_ENABLED
- if (window_id == DisplayServer::MAIN_WINDOW_ID) {
- // Append a suffix to the window title to denote that the project is running
- // from a debug build (including the editor). Since this results in lower performance,
- // this should be clearly presented to the user.
- tr_title = vformat("%s (DEBUG)", tr_title);
- }
-#endif
DisplayServer::get_singleton()->window_set_title(tr_title, window_id);
DisplayServer::get_singleton()->window_attach_instance_id(get_instance_id(), window_id);
@@ -994,6 +986,12 @@ void Window::_update_window_size() {
}
DisplayServer::get_singleton()->window_set_max_size(max_size_used, window_id);
+
+ if (keep_title_visible) {
+ Size2i title_size = DisplayServer::get_singleton()->window_get_title_size(tr_title, window_id);
+ size_limit = size_limit.max(title_size);
+ }
+
DisplayServer::get_singleton()->window_set_min_size(size_limit, window_id);
DisplayServer::get_singleton()->window_set_size(size, window_id);
}
@@ -1281,17 +1279,19 @@ void Window::_notification(int p_what) {
_invalidate_theme_cache();
_update_theme_item_cache();
- if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID) {
- String tr_title = atr(title);
+ tr_title = atr(title);
#ifdef DEBUG_ENABLED
- if (window_id == DisplayServer::MAIN_WINDOW_ID) {
- // Append a suffix to the window title to denote that the project is running
- // from a debug build (including the editor). Since this results in lower performance,
- // this should be clearly presented to the user.
- tr_title = vformat("%s (DEBUG)", tr_title);
- }
+ if (window_id == DisplayServer::MAIN_WINDOW_ID) {
+ // Append a suffix to the window title to denote that the project is running
+ // from a debug build (including the editor). Since this results in lower performance,
+ // this should be clearly presented to the user.
+ tr_title = vformat("%s (DEBUG)", tr_title);
+ }
#endif
+
+ if (!embedder && window_id != DisplayServer::INVALID_WINDOW_ID) {
DisplayServer::get_singleton()->window_set_title(tr_title, window_id);
+ _update_window_size();
}
} break;
@@ -1384,6 +1384,20 @@ Window::ContentScaleStretch Window::get_content_scale_stretch() const {
return content_scale_stretch;
}
+void Window::set_keep_title_visible(bool p_title_visible) {
+ if (keep_title_visible == p_title_visible) {
+ return;
+ }
+ keep_title_visible = p_title_visible;
+ if (window_id != DisplayServer::INVALID_WINDOW_ID) {
+ _update_window_size();
+ }
+}
+
+bool Window::get_keep_title_visible() const {
+ return keep_title_visible;
+}
+
void Window::set_content_scale_factor(real_t p_factor) {
ERR_MAIN_THREAD_GUARD;
ERR_FAIL_COND(p_factor <= 0);
@@ -2713,6 +2727,9 @@ void Window::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_content_scale_stretch", "stretch"), &Window::set_content_scale_stretch);
ClassDB::bind_method(D_METHOD("get_content_scale_stretch"), &Window::get_content_scale_stretch);
+ ClassDB::bind_method(D_METHOD("set_keep_title_visible", "title_visible"), &Window::set_keep_title_visible);
+ ClassDB::bind_method(D_METHOD("get_keep_title_visible"), &Window::get_keep_title_visible);
+
ClassDB::bind_method(D_METHOD("set_content_scale_factor", "factor"), &Window::set_content_scale_factor);
ClassDB::bind_method(D_METHOD("get_content_scale_factor"), &Window::get_content_scale_factor);
@@ -2826,6 +2843,7 @@ void Window::_bind_methods() {
ADD_GROUP("Limits", "");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "min_size", PROPERTY_HINT_NONE, "suffix:px"), "set_min_size", "get_min_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "max_size", PROPERTY_HINT_NONE, "suffix:px"), "set_max_size", "get_max_size");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_title_visible"), "set_keep_title_visible", "get_keep_title_visible");
ADD_GROUP("Content Scale", "content_scale_");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "content_scale_size"), "set_content_scale_size", "get_content_scale_size");
diff --git a/scene/main/window.h b/scene/main/window.h
index db739cc304..8a54b6c7d3 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -111,6 +111,7 @@ private:
bool initialized = false;
String title;
+ String tr_title;
mutable int current_screen = 0;
mutable Vector2i position;
mutable Size2i size = Size2i(DEFAULT_WINDOW_SIZE, DEFAULT_WINDOW_SIZE);
@@ -131,6 +132,7 @@ private:
bool updating_embedded_window = false;
bool clamp_to_embedder = false;
bool unparent_when_invisible = false;
+ bool keep_title_visible = false;
LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED;
@@ -336,6 +338,9 @@ public:
void set_content_scale_stretch(ContentScaleStretch p_stretch);
ContentScaleStretch get_content_scale_stretch() const;
+ void set_keep_title_visible(bool p_title_visible);
+ bool get_keep_title_visible() const;
+
void set_content_scale_factor(real_t p_factor);
real_t get_content_scale_factor() const;
diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp
index 7aa2f8e178..cb9cda59b2 100644
--- a/scene/resources/font.cpp
+++ b/scene/resources/font.cpp
@@ -558,24 +558,28 @@ _FORCE_INLINE_ void FontFile::_clear_cache() {
}
}
-_FORCE_INLINE_ void FontFile::_ensure_rid(int p_cache_index) const {
+_FORCE_INLINE_ void FontFile::_ensure_rid(int p_cache_index, int p_make_linked_from) const {
if (unlikely(p_cache_index >= cache.size())) {
cache.resize(p_cache_index + 1);
}
if (unlikely(!cache[p_cache_index].is_valid())) {
- cache.write[p_cache_index] = TS->create_font();
- TS->font_set_data_ptr(cache[p_cache_index], data_ptr, data_size);
- TS->font_set_antialiasing(cache[p_cache_index], antialiasing);
- TS->font_set_generate_mipmaps(cache[p_cache_index], mipmaps);
- TS->font_set_multichannel_signed_distance_field(cache[p_cache_index], msdf);
- TS->font_set_msdf_pixel_range(cache[p_cache_index], msdf_pixel_range);
- TS->font_set_msdf_size(cache[p_cache_index], msdf_size);
- TS->font_set_fixed_size(cache[p_cache_index], fixed_size);
- TS->font_set_force_autohinter(cache[p_cache_index], force_autohinter);
- TS->font_set_allow_system_fallback(cache[p_cache_index], allow_system_fallback);
- TS->font_set_hinting(cache[p_cache_index], hinting);
- TS->font_set_subpixel_positioning(cache[p_cache_index], subpixel_positioning);
- TS->font_set_oversampling(cache[p_cache_index], oversampling);
+ if (p_make_linked_from >= 0 && p_make_linked_from != p_cache_index && p_make_linked_from < cache.size()) {
+ cache.write[p_cache_index] = TS->create_font_linked_variation(cache[p_make_linked_from]);
+ } else {
+ cache.write[p_cache_index] = TS->create_font();
+ TS->font_set_data_ptr(cache[p_cache_index], data_ptr, data_size);
+ TS->font_set_antialiasing(cache[p_cache_index], antialiasing);
+ TS->font_set_generate_mipmaps(cache[p_cache_index], mipmaps);
+ TS->font_set_multichannel_signed_distance_field(cache[p_cache_index], msdf);
+ TS->font_set_msdf_pixel_range(cache[p_cache_index], msdf_pixel_range);
+ TS->font_set_msdf_size(cache[p_cache_index], msdf_size);
+ TS->font_set_fixed_size(cache[p_cache_index], fixed_size);
+ TS->font_set_force_autohinter(cache[p_cache_index], force_autohinter);
+ TS->font_set_allow_system_fallback(cache[p_cache_index], allow_system_fallback);
+ TS->font_set_hinting(cache[p_cache_index], hinting);
+ TS->font_set_subpixel_positioning(cache[p_cache_index], subpixel_positioning);
+ TS->font_set_oversampling(cache[p_cache_index], oversampling);
+ }
}
}
@@ -2218,17 +2222,19 @@ real_t FontFile::get_oversampling() const {
RID FontFile::find_variation(const Dictionary &p_variation_coordinates, int p_face_index, float p_strength, Transform2D p_transform, int p_spacing_top, int p_spacing_bottom, int p_spacing_space, int p_spacing_glyph) const {
// Find existing variation cache.
const Dictionary &supported_coords = get_supported_variation_list();
+ int make_linked_from = -1;
for (int i = 0; i < cache.size(); i++) {
if (cache[i].is_valid()) {
const Dictionary &cache_var = TS->font_get_variation_coordinates(cache[i]);
bool match = true;
+ bool match_linked = true;
match = match && (TS->font_get_face_index(cache[i]) == p_face_index);
match = match && (TS->font_get_embolden(cache[i]) == p_strength);
match = match && (TS->font_get_transform(cache[i]) == p_transform);
- match = match && (TS->font_get_spacing(cache[i], TextServer::SPACING_TOP) == p_spacing_top);
- match = match && (TS->font_get_spacing(cache[i], TextServer::SPACING_BOTTOM) == p_spacing_bottom);
- match = match && (TS->font_get_spacing(cache[i], TextServer::SPACING_SPACE) == p_spacing_space);
- match = match && (TS->font_get_spacing(cache[i], TextServer::SPACING_GLYPH) == p_spacing_glyph);
+ match_linked = match_linked && (TS->font_get_spacing(cache[i], TextServer::SPACING_TOP) == p_spacing_top);
+ match_linked = match_linked && (TS->font_get_spacing(cache[i], TextServer::SPACING_BOTTOM) == p_spacing_bottom);
+ match_linked = match_linked && (TS->font_get_spacing(cache[i], TextServer::SPACING_SPACE) == p_spacing_space);
+ match_linked = match_linked && (TS->font_get_spacing(cache[i], TextServer::SPACING_GLYPH) == p_spacing_glyph);
for (const Variant *V = supported_coords.next(nullptr); V && match; V = supported_coords.next(V)) {
const Vector3 &def = supported_coords[*V];
@@ -2255,22 +2261,34 @@ RID FontFile::find_variation(const Dictionary &p_variation_coordinates, int p_fa
match = match && (c_v == s_v);
}
if (match) {
- return cache[i];
+ if (match_linked) {
+ return cache[i];
+ } else {
+ make_linked_from = i;
+ }
}
}
}
// Create new variation cache.
int idx = cache.size();
- _ensure_rid(idx);
- TS->font_set_variation_coordinates(cache[idx], p_variation_coordinates);
- TS->font_set_face_index(cache[idx], p_face_index);
- TS->font_set_embolden(cache[idx], p_strength);
- TS->font_set_transform(cache[idx], p_transform);
- TS->font_set_spacing(cache[idx], TextServer::SPACING_TOP, p_spacing_top);
- TS->font_set_spacing(cache[idx], TextServer::SPACING_BOTTOM, p_spacing_bottom);
- TS->font_set_spacing(cache[idx], TextServer::SPACING_SPACE, p_spacing_space);
- TS->font_set_spacing(cache[idx], TextServer::SPACING_GLYPH, p_spacing_glyph);
+ if (make_linked_from >= 0) {
+ _ensure_rid(idx, make_linked_from);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_TOP, p_spacing_top);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_BOTTOM, p_spacing_bottom);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_SPACE, p_spacing_space);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_GLYPH, p_spacing_glyph);
+ } else {
+ _ensure_rid(idx);
+ TS->font_set_variation_coordinates(cache[idx], p_variation_coordinates);
+ TS->font_set_face_index(cache[idx], p_face_index);
+ TS->font_set_embolden(cache[idx], p_strength);
+ TS->font_set_transform(cache[idx], p_transform);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_TOP, p_spacing_top);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_BOTTOM, p_spacing_bottom);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_SPACE, p_spacing_space);
+ TS->font_set_spacing(cache[idx], TextServer::SPACING_GLYPH, p_spacing_glyph);
+ }
return cache[idx];
}
@@ -3016,14 +3034,19 @@ void SystemFont::_update_base_font() {
continue;
}
- // If it's a font collection check all faces to match requested style.
+ // If it's a font collection check all faces to match requested style and name.
int best_score = 0;
for (int i = 0; i < file->get_face_count(); i++) {
+ int score = 0;
file->set_face_index(0, i);
+ const String n = file->get_font_name();
+ if (n.to_upper() == E.to_upper()) {
+ score += 80;
+ }
BitField<TextServer::FontStyle> style = file->get_font_style();
int font_weight = file->get_font_weight();
int font_stretch = file->get_font_stretch();
- int score = (20 - Math::abs(font_weight - weight) / 50);
+ score += (20 - Math::abs(font_weight - weight) / 50);
score += (20 - Math::abs(font_stretch - stretch) / 10);
if (bool(style & TextServer::FONT_ITALIC) == italic) {
score += 30;
@@ -3042,7 +3065,7 @@ void SystemFont::_update_base_font() {
file->set_face_index(0, face_indeces[0]);
// If it's a variable font, apply weight, stretch and italic coordinates to match requested style.
- if (best_score != 50) {
+ if (best_score != 150) {
Dictionary ftr = file->get_supported_variation_list();
if (ftr.has(TS->name_to_tag("width"))) {
ftr_stretch = stretch;
diff --git a/scene/resources/font.h b/scene/resources/font.h
index 30ae11a235..c61d8ed9d4 100644
--- a/scene/resources/font.h
+++ b/scene/resources/font.h
@@ -205,7 +205,7 @@ class FontFile : public Font {
mutable Vector<RID> cache;
_FORCE_INLINE_ void _clear_cache();
- _FORCE_INLINE_ void _ensure_rid(int p_cache_index) const;
+ _FORCE_INLINE_ void _ensure_rid(int p_cache_index, int p_make_linked_from = -1) const;
void _convert_packed_8bit(Ref<Image> &p_source, int p_page, int p_sz);
void _convert_packed_4bit(Ref<Image> &p_source, int p_page, int p_sz);
diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp
index 1483d29384..ddec5e826b 100644
--- a/scene/theme/default_theme.cpp
+++ b/scene/theme/default_theme.cpp
@@ -498,8 +498,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("executing_line", "CodeEdit", icons["arrow_right"]);
theme->set_icon("can_fold", "CodeEdit", icons["arrow_down"]);
theme->set_icon("folded", "CodeEdit", icons["arrow_right"]);
- theme->set_icon("can_fold_code_region", "CodeEdit", icons["folder_down_arrow"]);
- theme->set_icon("folded_code_region", "CodeEdit", icons["folder_right_arrow"]);
+ theme->set_icon("can_fold_code_region", "CodeEdit", icons["region_unfolded"]);
+ theme->set_icon("folded_code_region", "CodeEdit", icons["region_folded"]);
theme->set_icon("folded_eol_icon", "CodeEdit", icons["text_edit_ellipsis"]);
theme->set_font("font", "CodeEdit", Ref<Font>());
diff --git a/scene/theme/icons/folder_down_arrow.svg b/scene/theme/icons/folder_down_arrow.svg
deleted file mode 100644
index 3bc4f3f73b..0000000000
--- a/scene/theme/icons/folder_down_arrow.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm1 5a1 1 0 0 1 1.414-1.414L6 6.172l1.586-1.586A1 1 0 0 1 9 6L6.707 8.293a1 1 0 0 1-1.414 0Z" fill="#fff"/></svg> \ No newline at end of file
diff --git a/scene/theme/icons/folder_right_arrow.svg b/scene/theme/icons/folder_right_arrow.svg
deleted file mode 100644
index a9b81d54f3..0000000000
--- a/scene/theme/icons/folder_right_arrow.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M2 1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H6V2a1 1 0 0 0-1-1zm3.5 8a1 1 0 0 1-1.414-1.414L5.672 6 4.086 4.414A1 1 0 0 1 5.5 3l2.293 2.293a1 1 0 0 1 0 1.414Z" fill="#fff"/></svg> \ No newline at end of file
diff --git a/scene/theme/icons/region_folded.svg b/scene/theme/icons/region_folded.svg
new file mode 100644
index 0000000000..245371b5a1
--- /dev/null
+++ b/scene/theme/icons/region_folded.svg
@@ -0,0 +1 @@
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M3 2a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1zm2.75 7a1 1 0 0 1-1.414-1.414L5.922 6 4.336 4.414A1 1 0 0 1 5.75 3l2.293 2.293a1 1 0 0 1 0 1.414z" fill="#fff"/></svg>
diff --git a/scene/theme/icons/region_unfolded.svg b/scene/theme/icons/region_unfolded.svg
new file mode 100644
index 0000000000..744ea7197d
--- /dev/null
+++ b/scene/theme/icons/region_unfolded.svg
@@ -0,0 +1 @@
+<svg height="12" width="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M10 3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1zM3 5.75a1 1 0 0 1 1.414-1.414L6 5.922l1.586-1.586A1 1 0 0 1 9 5.75L6.707 8.043a1 1 0 0 1-1.414 0z" fill="#fff"/></svg>
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index f41238b075..6459cc7462 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -82,6 +82,10 @@ int DisplayServer::global_menu_add_multistate_item(const String &p_menu_root, co
return -1;
}
+void DisplayServer::global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callbacs, const Callable &p_close_callback) {
+ WARN_PRINT("Global menus not supported by this display server.");
+}
+
int DisplayServer::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
WARN_PRINT("Global menus not supported by this display server.");
return -1;
@@ -106,6 +110,10 @@ void DisplayServer::global_menu_set_item_callback(const String &p_menu_root, int
WARN_PRINT("Global menus not supported by this display server.");
}
+void DisplayServer::global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback) {
+ WARN_PRINT("Global menus not supported by this display server.");
+}
+
void DisplayServer::global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback) {
WARN_PRINT("Global menus not supported by this display server.");
}
@@ -160,6 +168,11 @@ bool DisplayServer::global_menu_is_item_disabled(const String &p_menu_root, int
return false;
}
+bool DisplayServer::global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const {
+ WARN_PRINT("Global menus not supported by this display server.");
+ return false;
+}
+
String DisplayServer::global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const {
WARN_PRINT("Global menus not supported by this display server.");
return String();
@@ -217,6 +230,10 @@ void DisplayServer::global_menu_set_item_disabled(const String &p_menu_root, int
WARN_PRINT("Global menus not supported by this display server.");
}
+void DisplayServer::global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden) {
+ WARN_PRINT("Global menus not supported by this display server.");
+}
+
void DisplayServer::global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip) {
WARN_PRINT("Global menus not supported by this display server.");
}
@@ -581,6 +598,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_feature", "feature"), &DisplayServer::has_feature);
ClassDB::bind_method(D_METHOD("get_name"), &DisplayServer::get_name);
+ ClassDB::bind_method(D_METHOD("global_menu_set_popup_callbacks", "menu_root", "open_callback", "close_callback"), &DisplayServer::global_menu_set_popup_callbacks);
ClassDB::bind_method(D_METHOD("global_menu_add_submenu_item", "menu_root", "label", "submenu", "index"), &DisplayServer::global_menu_add_submenu_item, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("global_menu_add_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("global_menu_add_check_item", "menu_root", "label", "callback", "key_callback", "tag", "accelerator", "index"), &DisplayServer::global_menu_add_check_item, DEFVAL(Callable()), DEFVAL(Callable()), DEFVAL(Variant()), DEFVAL(Key::NONE), DEFVAL(-1));
@@ -604,6 +622,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("global_menu_get_item_submenu", "menu_root", "idx"), &DisplayServer::global_menu_get_item_submenu);
ClassDB::bind_method(D_METHOD("global_menu_get_item_accelerator", "menu_root", "idx"), &DisplayServer::global_menu_get_item_accelerator);
ClassDB::bind_method(D_METHOD("global_menu_is_item_disabled", "menu_root", "idx"), &DisplayServer::global_menu_is_item_disabled);
+ ClassDB::bind_method(D_METHOD("global_menu_is_item_hidden", "menu_root", "idx"), &DisplayServer::global_menu_is_item_hidden);
ClassDB::bind_method(D_METHOD("global_menu_get_item_tooltip", "menu_root", "idx"), &DisplayServer::global_menu_get_item_tooltip);
ClassDB::bind_method(D_METHOD("global_menu_get_item_state", "menu_root", "idx"), &DisplayServer::global_menu_get_item_state);
ClassDB::bind_method(D_METHOD("global_menu_get_item_max_states", "menu_root", "idx"), &DisplayServer::global_menu_get_item_max_states);
@@ -614,12 +633,14 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("global_menu_set_item_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_checkable);
ClassDB::bind_method(D_METHOD("global_menu_set_item_radio_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_radio_checkable);
ClassDB::bind_method(D_METHOD("global_menu_set_item_callback", "menu_root", "idx", "callback"), &DisplayServer::global_menu_set_item_callback);
+ ClassDB::bind_method(D_METHOD("global_menu_set_item_hover_callbacks", "menu_root", "idx", "callback"), &DisplayServer::global_menu_set_item_hover_callbacks);
ClassDB::bind_method(D_METHOD("global_menu_set_item_key_callback", "menu_root", "idx", "key_callback"), &DisplayServer::global_menu_set_item_key_callback);
ClassDB::bind_method(D_METHOD("global_menu_set_item_tag", "menu_root", "idx", "tag"), &DisplayServer::global_menu_set_item_tag);
ClassDB::bind_method(D_METHOD("global_menu_set_item_text", "menu_root", "idx", "text"), &DisplayServer::global_menu_set_item_text);
ClassDB::bind_method(D_METHOD("global_menu_set_item_submenu", "menu_root", "idx", "submenu"), &DisplayServer::global_menu_set_item_submenu);
ClassDB::bind_method(D_METHOD("global_menu_set_item_accelerator", "menu_root", "idx", "keycode"), &DisplayServer::global_menu_set_item_accelerator);
ClassDB::bind_method(D_METHOD("global_menu_set_item_disabled", "menu_root", "idx", "disabled"), &DisplayServer::global_menu_set_item_disabled);
+ ClassDB::bind_method(D_METHOD("global_menu_set_item_hidden", "menu_root", "idx", "hidden"), &DisplayServer::global_menu_set_item_hidden);
ClassDB::bind_method(D_METHOD("global_menu_set_item_tooltip", "menu_root", "idx", "tooltip"), &DisplayServer::global_menu_set_item_tooltip);
ClassDB::bind_method(D_METHOD("global_menu_set_item_state", "menu_root", "idx", "state"), &DisplayServer::global_menu_set_item_state);
ClassDB::bind_method(D_METHOD("global_menu_set_item_max_states", "menu_root", "idx", "max_states"), &DisplayServer::global_menu_set_item_max_states);
@@ -696,6 +717,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("window_get_popup_safe_rect", "window"), &DisplayServer::window_get_popup_safe_rect);
ClassDB::bind_method(D_METHOD("window_set_title", "title", "window_id"), &DisplayServer::window_set_title, DEFVAL(MAIN_WINDOW_ID));
+ ClassDB::bind_method(D_METHOD("window_get_title_size", "title", "window_id"), &DisplayServer::window_get_title_size, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_set_mouse_passthrough", "region", "window_id"), &DisplayServer::window_set_mouse_passthrough, DEFVAL(MAIN_WINDOW_ID));
ClassDB::bind_method(D_METHOD("window_get_current_screen", "window_id"), &DisplayServer::window_get_current_screen, DEFVAL(MAIN_WINDOW_ID));
diff --git a/servers/display_server.h b/servers/display_server.h
index 85f9270696..d2e112d224 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -130,6 +130,8 @@ public:
virtual bool has_feature(Feature p_feature) const = 0;
virtual String get_name() const = 0;
+ virtual void global_menu_set_popup_callbacks(const String &p_menu_root, const Callable &p_open_callback = Callable(), const Callable &p_close_callback = Callable());
+
virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1);
virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Callable &p_key_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1);
@@ -153,6 +155,7 @@ public:
virtual String global_menu_get_item_submenu(const String &p_menu_root, int p_idx) const;
virtual Key global_menu_get_item_accelerator(const String &p_menu_root, int p_idx) const;
virtual bool global_menu_is_item_disabled(const String &p_menu_root, int p_idx) const;
+ virtual bool global_menu_is_item_hidden(const String &p_menu_root, int p_idx) const;
virtual String global_menu_get_item_tooltip(const String &p_menu_root, int p_idx) const;
virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const;
virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const;
@@ -164,11 +167,13 @@ public:
virtual void global_menu_set_item_radio_checkable(const String &p_menu_root, int p_idx, bool p_checkable);
virtual void global_menu_set_item_callback(const String &p_menu_root, int p_idx, const Callable &p_callback);
virtual void global_menu_set_item_key_callback(const String &p_menu_root, int p_idx, const Callable &p_key_callback);
+ virtual void global_menu_set_item_hover_callbacks(const String &p_menu_root, int p_idx, const Callable &p_callback);
virtual void global_menu_set_item_tag(const String &p_menu_root, int p_idx, const Variant &p_tag);
virtual void global_menu_set_item_text(const String &p_menu_root, int p_idx, const String &p_text);
virtual void global_menu_set_item_submenu(const String &p_menu_root, int p_idx, const String &p_submenu);
virtual void global_menu_set_item_accelerator(const String &p_menu_root, int p_idx, Key p_keycode);
virtual void global_menu_set_item_disabled(const String &p_menu_root, int p_idx, bool p_disabled);
+ virtual void global_menu_set_item_hidden(const String &p_menu_root, int p_idx, bool p_hidden);
virtual void global_menu_set_item_tooltip(const String &p_menu_root, int p_idx, const String &p_tooltip);
virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state);
virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states);
@@ -390,6 +395,7 @@ public:
virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) = 0;
virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) = 0;
+ virtual Size2i window_get_title_size(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) const { return Size2i(); }
virtual void window_set_mouse_passthrough(const Vector<Vector2> &p_region, WindowID p_window = MAIN_WINDOW_ID);
@@ -500,7 +506,8 @@ public:
FILE_DIALOG_MODE_OPEN_FILES,
FILE_DIALOG_MODE_OPEN_DIR,
FILE_DIALOG_MODE_OPEN_ANY,
- FILE_DIALOG_MODE_SAVE_FILE
+ FILE_DIALOG_MODE_SAVE_FILE,
+ FILE_DIALOG_MODE_SAVE_MAX
};
virtual Error file_dialog_show(const String &p_title, const String &p_current_directory, const String &p_filename, bool p_show_hidden, FileDialogMode p_mode, const Vector<String> &p_filters, const Callable &p_callback);
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
index 7eb8cbd02f..b9bda9329e 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
@@ -103,8 +103,9 @@ void RendererCompositorRD::begin_frame(double frame_step) {
}
void RendererCompositorRD::end_frame(bool p_swap_buffers) {
- // TODO: Likely pass a bool to swap buffers to avoid display?
- RD::get_singleton()->swap_buffers();
+ if (p_swap_buffers) {
+ RD::get_singleton()->swap_buffers();
+ }
}
void RendererCompositorRD::initialize() {
diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp
index 86631ae9d9..e8e0d2e3eb 100644
--- a/servers/rendering/renderer_viewport.cpp
+++ b/servers/rendering/renderer_viewport.cpp
@@ -616,7 +616,7 @@ void RendererViewport::_draw_viewport(Viewport *p_viewport) {
}
}
-void RendererViewport::draw_viewports() {
+void RendererViewport::draw_viewports(bool p_swap_buffers) {
timestamp_vp_map.clear();
// get our xr interface in case we need it
@@ -732,7 +732,7 @@ void RendererViewport::draw_viewports() {
// commit our eyes
Vector<BlitToScreen> blits = xr_interface->post_draw_viewport(vp->render_target, vp->viewport_to_screen_rect);
if (vp->viewport_to_screen != DisplayServer::INVALID_WINDOW_ID) {
- if (OS::get_singleton()->get_current_rendering_driver_name() == "opengl3" || OS::get_singleton()->get_current_rendering_driver_name() == "opengl3_angle") {
+ if (OS::get_singleton()->get_current_rendering_driver_name().begins_with("opengl3")) {
if (blits.size() > 0) {
RSG::rasterizer->blit_render_targets_to_screen(vp->viewport_to_screen, blits.ptr(), blits.size());
}
@@ -771,7 +771,7 @@ void RendererViewport::draw_viewports() {
blit_to_screen_list[vp->viewport_to_screen] = Vector<BlitToScreen>();
}
- if (OS::get_singleton()->get_current_rendering_driver_name() == "opengl3" || OS::get_singleton()->get_current_rendering_driver_name() == "opengl3_angle") {
+ if (OS::get_singleton()->get_current_rendering_driver_name().begins_with("opengl3")) {
Vector<BlitToScreen> blit_to_screen_vec;
blit_to_screen_vec.push_back(blit);
RSG::rasterizer->blit_render_targets_to_screen(vp->viewport_to_screen, blit_to_screen_vec.ptr(), 1);
@@ -799,11 +799,14 @@ void RendererViewport::draw_viewports() {
total_draw_calls_used = draw_calls_used;
RENDER_TIMESTAMP("< Render Viewports");
- //this needs to be called to make screen swapping more efficient
- RSG::rasterizer->prepare_for_blitting_render_targets();
- for (const KeyValue<int, Vector<BlitToScreen>> &E : blit_to_screen_list) {
- RSG::rasterizer->blit_render_targets_to_screen(E.key, E.value.ptr(), E.value.size());
+ if (p_swap_buffers) {
+ //this needs to be called to make screen swapping more efficient
+ RSG::rasterizer->prepare_for_blitting_render_targets();
+
+ for (const KeyValue<int, Vector<BlitToScreen>> &E : blit_to_screen_list) {
+ RSG::rasterizer->blit_render_targets_to_screen(E.key, E.value.ptr(), E.value.size());
+ }
}
}
diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h
index 44de6d8804..a0ec9e6318 100644
--- a/servers/rendering/renderer_viewport.h
+++ b/servers/rendering/renderer_viewport.h
@@ -299,7 +299,7 @@ public:
void handle_timestamp(String p_timestamp, uint64_t p_cpu_time, uint64_t p_gpu_time);
void set_default_clear_color(const Color &p_color);
- void draw_viewports();
+ void draw_viewports(bool p_swap_buffers);
bool free(RID p_rid);
diff --git a/servers/rendering/rendering_server_default.cpp b/servers/rendering/rendering_server_default.cpp
index b13d33de9e..2c8265b7d7 100644
--- a/servers/rendering/rendering_server_default.cpp
+++ b/servers/rendering/rendering_server_default.cpp
@@ -88,10 +88,10 @@ void RenderingServerDefault::_draw(bool p_swap_buffers, double frame_step) {
RSG::scene->render_probes();
- RSG::viewport->draw_viewports();
+ RSG::viewport->draw_viewports(p_swap_buffers);
RSG::canvas_render->update();
- if (OS::get_singleton()->get_current_rendering_driver_name() != "opengl3" && OS::get_singleton()->get_current_rendering_driver_name() != "opengl3_angle") {
+ if (!OS::get_singleton()->get_current_rendering_driver_name().begins_with("opengl3")) {
// Already called for gl_compatibility renderer.
RSG::rasterizer->end_frame(p_swap_buffers);
}
diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp
index 671134fb06..71f821205e 100644
--- a/servers/rendering/shader_compiler.cpp
+++ b/servers/rendering/shader_compiler.cpp
@@ -303,8 +303,8 @@ String ShaderCompiler::_get_sampler_name(ShaderLanguage::TextureFilter p_filter,
void ShaderCompiler::_dump_function_deps(const SL::ShaderNode *p_node, const StringName &p_for_func, const HashMap<StringName, String> &p_func_code, String &r_to_add, HashSet<StringName> &added) {
int fidx = -1;
- for (int i = 0; i < p_node->functions.size(); i++) {
- if (p_node->functions[i].name == p_for_func) {
+ for (int i = 0; i < p_node->vfunctions.size(); i++) {
+ if (p_node->vfunctions[i].name == p_for_func) {
fidx = i;
break;
}
@@ -314,7 +314,7 @@ void ShaderCompiler::_dump_function_deps(const SL::ShaderNode *p_node, const Str
Vector<StringName> uses_functions;
- for (const StringName &E : p_node->functions[fidx].uses_function) {
+ for (const StringName &E : p_node->vfunctions[fidx].uses_function) {
uses_functions.push_back(E);
}
uses_functions.sort_custom<StringName::AlphCompare>(); //ensure order is deterministic so the same shader is always produced
@@ -328,9 +328,9 @@ void ShaderCompiler::_dump_function_deps(const SL::ShaderNode *p_node, const Str
SL::FunctionNode *fnode = nullptr;
- for (int i = 0; i < p_node->functions.size(); i++) {
- if (p_node->functions[i].name == uses_functions[k]) {
- fnode = p_node->functions[i].function;
+ for (int i = 0; i < p_node->vfunctions.size(); i++) {
+ if (p_node->vfunctions[i].name == uses_functions[k]) {
+ fnode = p_node->vfunctions[i].function;
break;
}
}
@@ -765,8 +765,8 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
HashMap<StringName, String> function_code;
//code for functions
- for (int i = 0; i < pnode->functions.size(); i++) {
- SL::FunctionNode *fnode = pnode->functions[i].function;
+ for (int i = 0; i < pnode->vfunctions.size(); i++) {
+ SL::FunctionNode *fnode = pnode->vfunctions[i].function;
function = fnode;
current_func_name = fnode->name;
function_code[fnode->name] = _dump_node_code(fnode->body, p_level + 1, r_gen_code, p_actions, p_default_actions, p_assigning);
@@ -777,8 +777,8 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
HashSet<StringName> added_funcs_per_stage[STAGE_MAX];
- for (int i = 0; i < pnode->functions.size(); i++) {
- SL::FunctionNode *fnode = pnode->functions[i].function;
+ for (int i = 0; i < pnode->vfunctions.size(); i++) {
+ SL::FunctionNode *fnode = pnode->vfunctions[i].function;
function = fnode;
@@ -1150,9 +1150,9 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
const bool is_internal_func = internal_functions.has(vnode->name);
if (!is_internal_func) {
- for (int i = 0; i < shader->functions.size(); i++) {
- if (shader->functions[i].name == vnode->name) {
- func = shader->functions[i].function;
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (shader->vfunctions[i].name == vnode->name) {
+ func = shader->vfunctions[i].function;
break;
}
}
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 6f3280e6a1..34ffc1e90f 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -1455,17 +1455,17 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea
return true;
}
- for (int i = 0; i < shader->functions.size(); i++) {
- if (!shader->functions[i].callable) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable) {
continue;
}
- if (shader->functions[i].name == p_identifier) {
+ if (shader->vfunctions[i].name == p_identifier) {
if (r_data_type) {
- *r_data_type = shader->functions[i].function->return_type;
+ *r_data_type = shader->vfunctions[i].function->return_type;
}
if (r_array_size) {
- *r_array_size = shader->functions[i].function->return_array_size;
+ *r_array_size = shader->vfunctions[i].function->return_array_size;
}
if (r_type) {
*r_type = IDENTIFIER_FUNCTION;
@@ -3413,18 +3413,18 @@ bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionI
bool exists = false;
String arg_list = "";
- for (int i = 0; i < shader->functions.size(); i++) {
- if (name != shader->functions[i].name) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (name != shader->vfunctions[i].name) {
continue;
}
exists = true;
- if (!shader->functions[i].callable) {
+ if (!shader->vfunctions[i].callable) {
_set_error(vformat(RTR("Function '%s' can't be called from source code."), String(name)));
return false;
}
- FunctionNode *pfunc = shader->functions[i].function;
+ FunctionNode *pfunc = shader->vfunctions[i].function;
if (arg_list.is_empty()) {
for (int j = 0; j < pfunc->arguments.size(); j++) {
if (j > 0) {
@@ -4662,10 +4662,10 @@ bool ShaderLanguage::_validate_assign(Node *p_node, const FunctionInfo &p_functi
}
bool ShaderLanguage::_propagate_function_call_sampler_uniform_settings(StringName p_name, int p_argument, TextureFilter p_filter, TextureRepeat p_repeat) {
- for (int i = 0; i < shader->functions.size(); i++) {
- if (shader->functions[i].name == p_name) {
- ERR_FAIL_INDEX_V(p_argument, shader->functions[i].function->arguments.size(), false);
- FunctionNode::Argument *arg = &shader->functions[i].function->arguments.write[p_argument];
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (shader->vfunctions[i].name == p_name) {
+ ERR_FAIL_INDEX_V(p_argument, shader->vfunctions[i].function->arguments.size(), false);
+ FunctionNode::Argument *arg = &shader->vfunctions[i].function->arguments.write[p_argument];
if (arg->tex_builtin_check) {
_set_error(vformat(RTR("Sampler argument %d of function '%s' called more than once using both built-ins and uniform textures, this is not supported (use either one or the other)."), p_argument, String(p_name)));
return false;
@@ -4696,10 +4696,10 @@ bool ShaderLanguage::_propagate_function_call_sampler_uniform_settings(StringNam
}
bool ShaderLanguage::_propagate_function_call_sampler_builtin_reference(StringName p_name, int p_argument, const StringName &p_builtin) {
- for (int i = 0; i < shader->functions.size(); i++) {
- if (shader->functions[i].name == p_name) {
- ERR_FAIL_INDEX_V(p_argument, shader->functions[i].function->arguments.size(), false);
- FunctionNode::Argument *arg = &shader->functions[i].function->arguments.write[p_argument];
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (shader->vfunctions[i].name == p_name) {
+ ERR_FAIL_INDEX_V(p_argument, shader->vfunctions[i].function->arguments.size(), false);
+ FunctionNode::Argument *arg = &shader->vfunctions[i].function->arguments.write[p_argument];
if (arg->tex_argument_check) {
_set_error(vformat(RTR("Sampler argument %d of function '%s' called more than once using both built-ins and uniform textures, this is not supported (use either one or the other)."), p_argument, String(p_name)));
return false;
@@ -5229,11 +5229,13 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
expr = func;
} else { //a function call
- if (p_block == nullptr) { // Non-constructor function call in global space is forbidden.
- if (is_const_decl) {
+
+ // Non-builtin function call is forbidden for constant declaration.
+ if (is_const_decl) {
+ if (shader->functions.has(identifier)) {
_set_error(RTR("Expected constant expression."));
+ return nullptr;
}
- return nullptr;
}
const StringName &name = identifier;
@@ -5260,12 +5262,12 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
//test if function was parsed first
int function_index = -1;
- for (int i = 0; i < shader->functions.size(); i++) {
- if (shader->functions[i].name == name) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (shader->vfunctions[i].name == name) {
//add to current function as dependency
- for (int j = 0; j < shader->functions.size(); j++) {
- if (shader->functions[j].name == current_function) {
- shader->functions.write[j].uses_function.insert(name);
+ for (int j = 0; j < shader->vfunctions.size(); j++) {
+ if (shader->vfunctions[j].name == current_function) {
+ shader->vfunctions.write[j].uses_function.insert(name);
break;
}
}
@@ -5298,7 +5300,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
//connect texture arguments, so we can cache in the
//argument what type of filter and repeat to use
- FunctionNode *call_function = shader->functions[function_index].function;
+ FunctionNode *call_function = shader->vfunctions[function_index].function;
if (call_function) {
func->return_cache = call_function->get_datatype();
func->struct_name = call_function->get_datatype_name();
@@ -9476,8 +9478,8 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
}
}
- for (int i = 0; i < shader->functions.size(); i++) {
- if (!shader->functions[i].callable && shader->functions[i].name == name) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable && shader->vfunctions[i].name == name) {
_set_redefinition_error(String(name));
return ERR_PARSE_ERROR;
}
@@ -9492,7 +9494,8 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
function.function = func_node;
- shader->functions.push_back(function);
+ shader->functions.insert(name, function);
+ shader->vfunctions.push_back(function);
func_node->name = name;
func_node->return_type = type;
@@ -10136,8 +10139,8 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
continue;
}
bool found = false;
- for (int i = 0; i < shader->functions.size(); i++) {
- if (shader->functions[i].name == E.key) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (shader->vfunctions[i].name == E.key) {
found = true;
break;
}
@@ -10257,11 +10260,11 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
}
}
- for (int i = 0; i < shader->functions.size(); i++) {
- if (!shader->functions[i].callable || shader->functions[i].name == skip_function) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable || shader->vfunctions[i].name == skip_function) {
continue;
}
- matches.insert(String(shader->functions[i].name), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
+ matches.insert(String(shader->vfunctions[i].name), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
}
int idx = 0;
@@ -10319,26 +10322,26 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
block = block->parent_block;
}
- for (int i = 0; i < shader->functions.size(); i++) {
- if (!shader->functions[i].callable) {
+ for (int i = 0; i < shader->vfunctions.size(); i++) {
+ if (!shader->vfunctions[i].callable) {
continue;
}
- if (shader->functions[i].name == completion_function) {
+ if (shader->vfunctions[i].name == completion_function) {
String calltip;
- calltip += get_datatype_name(shader->functions[i].function->return_type);
+ calltip += get_datatype_name(shader->vfunctions[i].function->return_type);
- if (shader->functions[i].function->return_array_size > 0) {
+ if (shader->vfunctions[i].function->return_array_size > 0) {
calltip += "[";
- calltip += itos(shader->functions[i].function->return_array_size);
+ calltip += itos(shader->vfunctions[i].function->return_array_size);
calltip += "]";
}
calltip += " ";
- calltip += shader->functions[i].name;
+ calltip += shader->vfunctions[i].name;
calltip += "(";
- for (int j = 0; j < shader->functions[i].function->arguments.size(); j++) {
+ for (int j = 0; j < shader->vfunctions[i].function->arguments.size(); j++) {
if (j > 0) {
calltip += ", ";
} else {
@@ -10349,25 +10352,25 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
calltip += char32_t(0xFFFF);
}
- if (shader->functions[i].function->arguments[j].is_const) {
+ if (shader->vfunctions[i].function->arguments[j].is_const) {
calltip += "const ";
}
- if (shader->functions[i].function->arguments[j].qualifier != ArgumentQualifier::ARGUMENT_QUALIFIER_IN) {
- if (shader->functions[i].function->arguments[j].qualifier == ArgumentQualifier::ARGUMENT_QUALIFIER_OUT) {
+ if (shader->vfunctions[i].function->arguments[j].qualifier != ArgumentQualifier::ARGUMENT_QUALIFIER_IN) {
+ if (shader->vfunctions[i].function->arguments[j].qualifier == ArgumentQualifier::ARGUMENT_QUALIFIER_OUT) {
calltip += "out ";
} else { // ArgumentQualifier::ARGUMENT_QUALIFIER_INOUT
calltip += "inout ";
}
}
- calltip += get_datatype_name(shader->functions[i].function->arguments[j].type);
+ calltip += get_datatype_name(shader->vfunctions[i].function->arguments[j].type);
calltip += " ";
- calltip += shader->functions[i].function->arguments[j].name;
+ calltip += shader->vfunctions[i].function->arguments[j].name;
- if (shader->functions[i].function->arguments[j].array_size > 0) {
+ if (shader->vfunctions[i].function->arguments[j].array_size > 0) {
calltip += "[";
- calltip += itos(shader->functions[i].function->arguments[j].array_size);
+ calltip += itos(shader->vfunctions[i].function->arguments[j].array_size);
calltip += "]";
}
@@ -10376,7 +10379,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
}
}
- if (shader->functions[i].function->arguments.size()) {
+ if (shader->vfunctions[i].function->arguments.size()) {
calltip += " ";
}
calltip += ")";
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index 90fde72152..c707f6aad7 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -711,9 +711,10 @@ public:
HashMap<StringName, Varying> varyings;
HashMap<StringName, Uniform> uniforms;
HashMap<StringName, Struct> structs;
+ HashMap<StringName, Function> functions;
Vector<StringName> render_modes;
- Vector<Function> functions;
+ Vector<Function> vfunctions;
Vector<Constant> vconstants;
Vector<Struct> vstructs;
diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp
index 91a2c9797e..e584146d64 100644
--- a/servers/text/text_server_extension.cpp
+++ b/servers/text/text_server_extension.cpp
@@ -51,6 +51,7 @@ void TextServerExtension::_bind_methods() {
/* Font interface */
GDVIRTUAL_BIND(_create_font);
+ GDVIRTUAL_BIND(_create_font_linked_variation, "font_rid");
GDVIRTUAL_BIND(_font_set_data, "font_rid", "data");
GDVIRTUAL_BIND(_font_set_data_ptr, "font_rid", "data_ptr", "data_size");
@@ -412,6 +413,12 @@ RID TextServerExtension::create_font() {
return ret;
}
+RID TextServerExtension::create_font_linked_variation(const RID &p_font_rid) {
+ RID ret;
+ GDVIRTUAL_CALL(_create_font_linked_variation, p_font_rid, ret);
+ return ret;
+}
+
void TextServerExtension::font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) {
GDVIRTUAL_CALL(_font_set_data, p_font_rid, p_data);
}
diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h
index 0fa483f304..c40230e723 100644
--- a/servers/text/text_server_extension.h
+++ b/servers/text/text_server_extension.h
@@ -80,6 +80,9 @@ public:
virtual RID create_font() override;
GDVIRTUAL0R(RID, _create_font);
+ virtual RID create_font_linked_variation(const RID &p_font_rid) override;
+ GDVIRTUAL1R(RID, _create_font_linked_variation, RID);
+
virtual void font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) override;
virtual void font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) override;
GDVIRTUAL2(_font_set_data, RID, const PackedByteArray &);
diff --git a/servers/text_server.cpp b/servers/text_server.cpp
index 3c901f573e..e4eb4c730d 100644
--- a/servers/text_server.cpp
+++ b/servers/text_server.cpp
@@ -206,6 +206,7 @@ void TextServer::_bind_methods() {
/* Font Interface */
ClassDB::bind_method(D_METHOD("create_font"), &TextServer::create_font);
+ ClassDB::bind_method(D_METHOD("create_font_linked_variation", "font_rid"), &TextServer::create_font_linked_variation);
ClassDB::bind_method(D_METHOD("font_set_data", "font_rid", "data"), &TextServer::font_set_data);
diff --git a/servers/text_server.h b/servers/text_server.h
index d0cfd87b64..260b44da8b 100644
--- a/servers/text_server.h
+++ b/servers/text_server.h
@@ -237,6 +237,7 @@ public:
/* Font interface */
virtual RID create_font() = 0;
+ virtual RID create_font_linked_variation(const RID &p_font_rid) = 0;
virtual void font_set_data(const RID &p_font_rid, const PackedByteArray &p_data) = 0;
virtual void font_set_data_ptr(const RID &p_font_rid, const uint8_t *p_data_ptr, int64_t p_data_size) = 0;
diff --git a/tests/scene/test_code_edit.h b/tests/scene/test_code_edit.h
index b44a47bf8a..27907b8bb5 100644
--- a/tests/scene/test_code_edit.h
+++ b/tests/scene/test_code_edit.h
@@ -3725,6 +3725,67 @@ TEST_CASE("[SceneTree][CodeEdit] completion") {
CHECK(code_edit->get_line(0) == "sstest");
}
+ SUBCASE("[CodeEdit] autocomplete currently selected option") {
+ code_edit->set_code_completion_enabled(true);
+ REQUIRE(code_edit->is_code_completion_enabled());
+
+ // Initially select item 0.
+ code_edit->insert_text_at_caret("te");
+ code_edit->set_caret_column(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Initially selected item should be 0.");
+
+ // After adding later options shouldn't update selection.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4"); // Added te4.
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Adding later options shouldn't update selection.");
+
+ code_edit->set_code_completion_selected_index(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te5", "te5"); // Added te5.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6"); // Added te6.
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 2, "Adding later options shouldn't update selection.");
+
+ // Removing elements after selected element shouldn't update selection.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te5", "te5"); // Removed te6.
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 2, "Removing elements after selected element shouldn't update selection.");
+
+ // Changing elements after selected element shouldn't update selection.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te1", "te1");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6"); // Changed te5->te6.
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 2, "Changing elements after selected element shouldn't update selection.");
+
+ // Changing elements before selected element should reset selection.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te2", "te2"); // Changed te1->te2.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6");
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Changing elements before selected element should reset selection.");
+
+ // Removing elements before selected element should reset selection.
+ code_edit->set_code_completion_selected_index(2);
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te3", "te3"); // Removed te2.
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te4", "te4");
+ code_edit->add_code_completion_option(CodeEdit::CodeCompletionKind::KIND_VARIABLE, "te6", "te6");
+ code_edit->update_code_completion_options();
+ CHECK_MESSAGE(code_edit->get_code_completion_selected_index() == 0, "Removing elements before selected element should reset selection.");
+ }
+
memdelete(code_edit);
}