diff options
159 files changed, 3027 insertions, 1142 deletions
diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp index 37967cdb48..e7e350687b 100644 --- a/core/extension/native_extension.cpp +++ b/core/extension/native_extension.cpp @@ -30,7 +30,7 @@ #include "native_extension.h" #include "core/config/project_settings.h" -#include "core/io/config_file.h" +#include "core/io/dir_access.h" #include "core/object/class_db.h" #include "core/object/method_bind.h" #include "core/os/os.h" @@ -39,6 +39,111 @@ String NativeExtension::get_extension_list_config_file() { return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg"); } +String NativeExtension::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) { + // First, check the explicit libraries. + if (p_config->has_section("libraries")) { + List<String> libraries; + p_config->get_section_keys("libraries", &libraries); + + // Iterate the libraries, finding the best matching tags. + String best_library_path; + Vector<String> best_library_tags; + for (const String &E : libraries) { + Vector<String> tags = E.split("."); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met && tags.size() > best_library_tags.size()) { + best_library_path = p_config->get_value("libraries", E); + best_library_tags = tags; + } + } + + if (!best_library_path.is_empty()) { + if (best_library_path.is_relative_path()) { + best_library_path = p_path.get_base_dir().path_join(best_library_path); + } + if (r_tags != nullptr) { + r_tags->append_array(best_library_tags); + } + return best_library_path; + } + } + + // Second, try to autodetect + String autodetect_library_prefix; + if (p_config->has_section_key("configuration", "autodetect_library_prefix")) { + autodetect_library_prefix = p_config->get_value("configuration", "autodetect_library_prefix"); + } + if (!autodetect_library_prefix.is_empty()) { + String autodetect_path = autodetect_library_prefix; + if (autodetect_path.is_relative_path()) { + autodetect_path = p_path.get_base_dir().path_join(autodetect_path); + } + + // Find the folder and file parts of the prefix. + String folder; + String file_prefix; + if (DirAccess::dir_exists_absolute(autodetect_path)) { + folder = autodetect_path; + } else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) { + folder = autodetect_path.get_base_dir(); + file_prefix = autodetect_path.get_file(); + } else { + ERR_FAIL_V_MSG(String(), vformat("Error in extension: %s. Could not find folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); + } + + // Open the folder. + Ref<DirAccess> dir = DirAccess::open(folder); + ERR_FAIL_COND_V_MSG(!dir.is_valid(), String(), vformat("Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"", p_path, autodetect_library_prefix)); + + // Iterate the files and check the prefixes, finding the best matching file. + String best_file; + Vector<String> best_file_tags; + dir->list_dir_begin(); + String file_name = dir->_get_next(); + while (file_name != "") { + if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) { + // Check if the files matches all requested feature tags. + String tags_str = file_name.trim_prefix(file_prefix); + tags_str = tags_str.trim_suffix(tags_str.get_extension()); + + Vector<String> tags = tags_str.split(".", false); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_has_feature(tag)) { + all_tags_met = false; + break; + } + } + + // If all tags are found in the feature list, and we found more tags than before, use this file. + if (all_tags_met && tags.size() > best_file_tags.size()) { + best_file_tags = tags; + best_file = file_name; + } + } + file_name = dir->_get_next(); + } + + if (!best_file.is_empty()) { + String library_path = folder.path_join(best_file); + if (r_tags != nullptr) { + r_tags->append_array(best_file_tags); + } + return library_path; + } + } + return String(); +} + class NativeExtensionMethodBind : public MethodBind { GDNativeExtensionClassMethodCall call_func; GDNativeExtensionClassMethodPtrCall ptrcall_func; @@ -415,28 +520,7 @@ Ref<Resource> NativeExtensionResourceLoader::load(const String &p_path, const St String entry_symbol = config->get_value("configuration", "entry_symbol"); - List<String> libraries; - - config->get_section_keys("libraries", &libraries); - - String library_path; - - for (const String &E : libraries) { - Vector<String> tags = E.split("."); - bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); - if (!OS::get_singleton()->has_feature(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met) { - library_path = config->get_value("libraries", E); - break; - } - } + String library_path = NativeExtension::find_extension_library(p_path, config, [](String p_feature) { return OS::get_singleton()->has_feature(p_feature); }); if (library_path.is_empty()) { if (r_error) { diff --git a/core/extension/native_extension.h b/core/extension/native_extension.h index ca6200cd1e..77654cf0a8 100644 --- a/core/extension/native_extension.h +++ b/core/extension/native_extension.h @@ -31,7 +31,10 @@ #ifndef NATIVE_EXTENSION_H #define NATIVE_EXTENSION_H +#include <functional> + #include "core/extension/gdnative_interface.h" +#include "core/io/config_file.h" #include "core/io/resource_loader.h" #include "core/object/ref_counted.h" @@ -65,6 +68,7 @@ protected: public: static String get_extension_list_config_file(); + static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr); Error open_library(const String &p_path, const String &p_entry_symbol); void close_library(); diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 6219ea70e4..20445a8b03 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -923,6 +923,35 @@ void ResourceLoader::clear_translation_remaps() { } } +void ResourceLoader::clear_thread_load_tasks() { + thread_load_mutex->lock(); + + for (KeyValue<String, ResourceLoader::ThreadLoadTask> &E : thread_load_tasks) { + switch (E.value.status) { + case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_LOADED: { + E.value.resource = Ref<Resource>(); + } break; + + case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_IN_PROGRESS: { + if (E.value.thread != nullptr) { + E.value.thread->wait_to_finish(); + memdelete(E.value.thread); + E.value.thread = nullptr; + } + E.value.resource = Ref<Resource>(); + } break; + + case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_FAILED: + default: { + // do nothing + } + } + } + thread_load_tasks.clear(); + + thread_load_mutex->unlock(); +} + void ResourceLoader::load_path_remaps() { if (!ProjectSettings::get_singleton()->has_setting("path_remap/remapped_paths")) { return; diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 243670b2d0..af10098bd8 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -219,6 +219,8 @@ public: static void load_translation_remaps(); static void clear_translation_remaps(); + static void clear_thread_load_tasks(); + static void set_load_callback(ResourceLoadedCallback p_callback); static ResourceLoaderImport import; diff --git a/core/object/object.h b/core/object/object.h index 3ad8391dd6..446550f91f 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -556,6 +556,7 @@ public: CONNECT_PERSIST = 2, // hint for scene to save this connection CONNECT_ONE_SHOT = 4, CONNECT_REFERENCE_COUNTED = 8, + CONNECT_INHERITED = 16, // Used in editor builds. }; struct Connection { diff --git a/core/os/os.cpp b/core/os/os.cpp index bbb2a94fe7..055385579f 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -353,20 +353,26 @@ bool OS::has_feature(const String &p_feature) { if (p_feature == "debug") { return true; } +#endif // DEBUG_ENABLED + +#ifdef TOOLS_ENABLED + if (p_feature == "editor") { + return true; + } #else - if (p_feature == "release") { + if (p_feature == "template") { return true; } -#endif -#ifdef TOOLS_ENABLED - if (p_feature == "editor") { +#ifdef DEBUG_ENABLED + if (p_feature == "template_debug") { return true; } #else - if (p_feature == "standalone") { + if (p_feature == "template_release" || p_feature == "release") { return true; } -#endif +#endif // DEBUG_ENABLED +#endif // TOOLS_ENABLED if (sizeof(void *) == 8 && p_feature == "64") { return true; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 894557516e..39b7cbee0d 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -940,7 +940,7 @@ bool Variant::is_zero() const { return reinterpret_cast<const Signal *>(_data._mem)->is_null(); } case STRING_NAME: { - return *reinterpret_cast<const StringName *>(_data._mem) != StringName(); + return *reinterpret_cast<const StringName *>(_data._mem) == StringName(); } case NODE_PATH: { return reinterpret_cast<const NodePath *>(_data._mem)->is_empty(); diff --git a/doc/classes/BackBufferCopy.xml b/doc/classes/BackBufferCopy.xml index 3c811e6226..b2c5a1756f 100644 --- a/doc/classes/BackBufferCopy.xml +++ b/doc/classes/BackBufferCopy.xml @@ -4,8 +4,8 @@ Copies a region of the screen (or the whole screen) to a buffer so it can be accessed in your shader scripts through the [code]texture(SCREEN_TEXTURE, ...)[/code] function. </brief_description> <description> - Node for back-buffering the currently-displayed screen. The region defined in the BackBufferCopy node is buffered with the content of the screen it covers, or the entire screen according to the copy mode set. Use the [code]texture(SCREEN_TEXTURE, ...)[/code] function in your shader scripts to access the buffer. - [b]Note:[/b] Since this node inherits from [Node2D] (and not [Control]), anchors and margins won't apply to child [Control]-derived nodes. This can be problematic when resizing the window. To avoid this, add [Control]-derived nodes as [i]siblings[/i] to the BackBufferCopy node instead of adding them as children. + Node for back-buffering the currently-displayed screen. The region defined in the [BackBufferCopy] node is buffered with the content of the screen it covers, or the entire screen according to the copy mode set. Use the [code]texture(SCREEN_TEXTURE, ...)[/code] function in your shader scripts to access the buffer. + [b]Note:[/b] Since this node inherits from [Node2D] (and not [Control]), anchors and margins won't apply to child [Control]-derived nodes. This can be problematic when resizing the window. To avoid this, add [Control]-derived nodes as [i]siblings[/i] to the [BackBufferCopy] node instead of adding them as children. </description> <tutorials> </tutorials> @@ -14,18 +14,18 @@ Buffer mode. See [enum CopyMode] constants. </member> <member name="rect" type="Rect2" setter="set_rect" getter="get_rect" default="Rect2(-100, -100, 200, 200)"> - The area covered by the BackBufferCopy. Only used if [member copy_mode] is [constant COPY_MODE_RECT]. + The area covered by the [BackBufferCopy]. Only used if [member copy_mode] is [constant COPY_MODE_RECT]. </member> </members> <constants> <constant name="COPY_MODE_DISABLED" value="0" enum="CopyMode"> - Disables the buffering mode. This means the BackBufferCopy node will directly use the portion of screen it covers. + Disables the buffering mode. This means the [BackBufferCopy] node will directly use the portion of screen it covers. </constant> <constant name="COPY_MODE_RECT" value="1" enum="CopyMode"> - BackBufferCopy buffers a rectangular region. + [BackBufferCopy] buffers a rectangular region. </constant> <constant name="COPY_MODE_VIEWPORT" value="2" enum="CopyMode"> - BackBufferCopy buffers the entire screen. + [BackBufferCopy] buffers the entire screen. </constant> </constants> </class> diff --git a/doc/classes/CollisionObject2D.xml b/doc/classes/CollisionObject2D.xml index ee69015ae1..3ed2c9d3de 100644 --- a/doc/classes/CollisionObject2D.xml +++ b/doc/classes/CollisionObject2D.xml @@ -157,7 +157,7 @@ <param index="0" name="owner_id" type="int" /> <param index="1" name="shape_id" type="int" /> <description> - Returns the [Shape2D] with the given id from the given shape owner. + Returns the [Shape2D] with the given ID from the given shape owner. </description> </method> <method name="shape_owner_get_shape_count" qualifiers="const"> @@ -172,7 +172,7 @@ <param index="0" name="owner_id" type="int" /> <param index="1" name="shape_id" type="int" /> <description> - Returns the child index of the [Shape2D] with the given id from the given shape owner. + Returns the child index of the [Shape2D] with the given ID from the given shape owner. </description> </method> <method name="shape_owner_get_transform" qualifiers="const"> diff --git a/doc/classes/CollisionObject3D.xml b/doc/classes/CollisionObject3D.xml index f10136521a..c302963b92 100644 --- a/doc/classes/CollisionObject3D.xml +++ b/doc/classes/CollisionObject3D.xml @@ -130,7 +130,7 @@ <param index="0" name="owner_id" type="int" /> <param index="1" name="shape_id" type="int" /> <description> - Returns the [Shape3D] with the given id from the given shape owner. + Returns the [Shape3D] with the given ID from the given shape owner. </description> </method> <method name="shape_owner_get_shape_count" qualifiers="const"> @@ -145,7 +145,7 @@ <param index="0" name="owner_id" type="int" /> <param index="1" name="shape_id" type="int" /> <description> - Returns the child index of the [Shape3D] with the given id from the given shape owner. + Returns the child index of the [Shape3D] with the given ID from the given shape owner. </description> </method> <method name="shape_owner_get_transform" qualifiers="const"> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 8a38deeebe..fd6aab6a49 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -1071,7 +1071,8 @@ Tells the parent [Container] nodes how they should resize and place the node on the Y axis. Use one of the [enum SizeFlags] constants to change the flags. See the constants to learn what each does. </member> <member name="theme" type="Theme" setter="set_theme" getter="get_theme"> - The [Theme] resource this node and all its [Control] children use. If a child node has its own [Theme] resource set, theme items are merged with child's definitions having higher priority. + The [Theme] resource this node and all its [Control] and [Window] children use. If a child node has its own [Theme] resource set, theme items are merged with child's definitions having higher priority. + [b]Note:[/b] [Window] styles will have no effect unless the window is embedded. </member> <member name="theme_type_variation" type="StringName" setter="set_theme_type_variation" getter="get_theme_type_variation" default="&"""> The name of a theme type variation used by this [Control] to look up its own theme items. When empty, the class name of the node is used (e.g. [code]Button[/code] for the [Button] control), as well as the class names of all parent classes (in order of inheritance). diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index c59bfbfe4f..29135376c5 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -719,6 +719,12 @@ [b]Note:[/b] This method is implemented on macOS, Windows and Linux (X11). </description> </method> + <method name="is_touchscreen_available" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if touch events are available (Android or iOS), the capability is detected on the Webplatform or if [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse] is [code]true[/code]. + </description> + </method> <method name="keyboard_get_current_layout" qualifiers="const"> <return type="int" /> <description> @@ -889,13 +895,6 @@ Returns [code]true[/code] if the screen should never be turned off by the operating system's power-saving measures. See also [method screen_set_keep_on]. </description> </method> - <method name="screen_is_touchscreen" qualifiers="const"> - <return type="bool" /> - <param index="0" name="screen" type="int" default="-1" /> - <description> - Returns [code]true[/code] if the screen can send touch events or if [member ProjectSettings.input_devices/pointing/emulate_touch_from_mouse] is [code]true[/code]. - </description> - </method> <method name="screen_set_keep_on"> <return type="void" /> <param index="0" name="enable" type="bool" /> @@ -1156,14 +1155,14 @@ <return type="Vector2i" /> <param index="0" name="window_id" type="int" default="0" /> <description> - Returns the position of the given window to on the screen. + Returns the position of the client area of the given window on the screen. </description> </method> - <method name="window_get_real_size" qualifiers="const"> + <method name="window_get_position_with_decorations" qualifiers="const"> <return type="Vector2i" /> <param index="0" name="window_id" type="int" default="0" /> <description> - 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]. + Returns the position of the given window on the screen including the borders drawn by the operating system. See also [method window_get_position]. </description> </method> <method name="window_get_safe_title_margins" qualifiers="const"> @@ -1177,7 +1176,14 @@ <return type="Vector2i" /> <param index="0" name="window_id" type="int" default="0" /> <description> - Returns the size of the window specified by [param window_id] (in pixels), excluding the borders drawn by the operating system. This is also called the "client area". See also [method window_get_real_size], [method window_set_size] and [method window_get_position]. + Returns the size of the window specified by [param window_id] (in pixels), excluding the borders drawn by the operating system. This is also called the "client area". See also [method window_get_size_with_decorations], [method window_set_size] and [method window_get_position]. + </description> + </method> + <method name="window_get_size_with_decorations" qualifiers="const"> + <return type="Vector2i" /> + <param index="0" name="window_id" type="int" default="0" /> + <description> + 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_vsync_mode" qualifiers="const"> @@ -1634,14 +1640,17 @@ Maximized window mode, i.e. [Window] will occupy whole screen area except task bar and still display its borders. Normally happens when the minimize button is pressed. </constant> <constant name="WINDOW_MODE_FULLSCREEN" value="3" enum="WindowMode"> - Full screen window mode. Note that this is not [i]exclusive[/i] full screen. On Windows and Linux (X11), 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 cover the entire display area of a screen, have no border or decorations. Display video mode is not changed. + [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"> - Exclusive full screen window mode. This mode is implemented on Windows and macOS only. On other platforms, it is equivalent to [constant WINDOW_MODE_FULLSCREEN]. - [b]On Windows:[/b] 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. - [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. - 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 cover the entire display area of a screen, have no border or decorations. Display 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 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="WINDOW_FLAG_RESIZE_DISABLED" value="0" enum="WindowFlags"> The window can't be resizing by dragging its resize grip. It's still possible to resize the window using [method window_set_size]. This flag is ignored for full screen windows. diff --git a/doc/classes/Light3D.xml b/doc/classes/Light3D.xml index 60e20cd97d..fe7756faaf 100644 --- a/doc/classes/Light3D.xml +++ b/doc/classes/Light3D.xml @@ -114,7 +114,7 @@ <member name="shadow_enabled" type="bool" setter="set_shadow" getter="has_shadow" default="false"> If [code]true[/code], the light will cast real-time shadows. This has a significant performance cost. Only enable shadow rendering when it makes a noticeable difference in the scene's appearance, and consider using [member distance_fade_enabled] to hide the light when far away from the [Camera3D]. </member> - <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" default="1.0"> + <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" default="2.0"> Offsets the lookup into the shadow map by the object's normal. This can be used to reduce self-shadowing artifacts without using [member shadow_bias]. In practice, this value should be tweaked along with [member shadow_bias] to reduce artifacts as much as possible. </member> <member name="shadow_opacity" type="float" setter="set_param" getter="get_param" default="1.0"> diff --git a/doc/classes/OmniLight3D.xml b/doc/classes/OmniLight3D.xml index 193ae8deeb..f71c81e713 100644 --- a/doc/classes/OmniLight3D.xml +++ b/doc/classes/OmniLight3D.xml @@ -20,7 +20,7 @@ <member name="omni_shadow_mode" type="int" setter="set_shadow_mode" getter="get_shadow_mode" enum="OmniLight3D.ShadowMode" default="1"> See [enum ShadowMode]. </member> - <member name="shadow_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="0.2" /> + <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="1.0" /> </members> <constants> <constant name="SHADOW_DUAL_PARABOLOID" value="0" enum="ShadowMode"> diff --git a/doc/classes/PhysicsPointQueryParameters2D.xml b/doc/classes/PhysicsPointQueryParameters2D.xml index e49d2a9f5f..76dc816dab 100644 --- a/doc/classes/PhysicsPointQueryParameters2D.xml +++ b/doc/classes/PhysicsPointQueryParameters2D.xml @@ -10,7 +10,7 @@ </tutorials> <members> <member name="canvas_instance_id" type="int" setter="set_canvas_instance_id" getter="get_canvas_instance_id" default="0"> - If different from [code]0[/code], restricts the query to a specific canvas layer specified by its instance id. See [method Object.get_instance_id]. + If different from [code]0[/code], restricts the query to a specific canvas layer specified by its instance ID. See [method Object.get_instance_id]. </member> <member name="collide_with_areas" type="bool" setter="set_collide_with_areas" getter="is_collide_with_areas_enabled" default="false"> If [code]true[/code], the query will take [Area2D]s into account. diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index a69163f429..6810b0e8e4 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -206,7 +206,7 @@ <return type="int" /> <param index="0" name="index" type="int" /> <description> - Returns the id of the item at the given [param index]. [code]id[/code] can be manually assigned, while index can not. + Returns the ID of the item at the given [param index]. [code]id[/code] can be manually assigned, while index can not. </description> </method> <method name="get_item_indent" qualifiers="const"> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 1f0a8d91fa..dce9d5a55c 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2299,6 +2299,9 @@ <member name="xr/openxr/reference_space" type="int" setter="" getter="" default=""1""> Specify the default reference space. </member> + <member name="xr/openxr/submit_depth_buffer" type="bool" setter="" getter="" default="false"> + If [code]true[/code], OpenXR will manage the depth buffer and use the depth buffer for advanced reprojection provided this is supported by the XR runtime. Note that some rendering features in Godot can't be used with this feature. + </member> <member name="xr/openxr/view_configuration" type="int" setter="" getter="" default=""1""> Specify the view configuration with which to configure OpenXR setting up either Mono or Stereo rendering. </member> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index 2ffa4dc50b..fc05f67416 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -1295,13 +1295,13 @@ <method name="get_test_cube"> <return type="RID" /> <description> - Returns the id of the test cube. Creates one if none exists. + Returns the ID of the test cube. Creates one if none exists. </description> </method> <method name="get_test_texture"> <return type="RID" /> <description> - Returns the id of the test texture. Creates one if none exists. + Returns the ID of the test texture. Creates one if none exists. </description> </method> <method name="get_video_adapter_api_version" qualifiers="const"> @@ -1335,7 +1335,7 @@ <method name="get_white_texture"> <return type="RID" /> <description> - Returns the id of a white texture. Creates one if none exists. + Returns the ID of a white texture. Creates one if none exists. </description> </method> <method name="gi_set_use_half_resolution"> diff --git a/doc/classes/SpotLight3D.xml b/doc/classes/SpotLight3D.xml index 28e2ef0d95..59d36aefab 100644 --- a/doc/classes/SpotLight3D.xml +++ b/doc/classes/SpotLight3D.xml @@ -12,6 +12,7 @@ </tutorials> <members> <member name="shadow_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="0.03" /> + <member name="shadow_normal_bias" type="float" setter="set_param" getter="get_param" overrides="Light3D" default="1.0" /> <member name="spot_angle" type="float" setter="set_param" getter="get_param" default="45.0"> The spotlight's angle in degrees. [b]Note:[/b] [member spot_angle] is not affected by [member Node3D.scale] (the light's scale or its parent's scale). diff --git a/doc/classes/String.xml b/doc/classes/String.xml index 61134afc2d..05af07ece1 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="String" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Built-in string class. + Built-in string Variant type. </brief_description> <description> - This is the built-in string class (and the one used by GDScript). It supports Unicode and provides all necessary means for string handling. Strings are reference-counted and use a copy-on-write approach, so passing them around is cheap in resources. + This is the built-in string Variant type (and the one used by GDScript). Strings may contain any number of Unicode characters, and expose methods useful for manipulating and generating strings. Strings are reference-counted and use a copy-on-write approach (every modification to a string returns a new [String]), so passing them around is cheap in resources. + Some string methods have corresponding variations. Variations suffixed with [code]n[/code] ([method countn], [method findn], [method replacen], etc.) are [b]case-insensitive[/b] (they make no distinction between uppercase and lowercase letters). Method variations prefixed with [code]r[/code] ([method rfind], [method rsplit], etc.) are reversed, and start from the end of the string, instead of the beginning. + [b]Note:[/b] In a boolean context, a string will evaluate to [code]false[/code] if it is empty ([code]""[/code]). Otherwise, a string will always evaluate to [code]true[/code]. </description> <tutorials> <link title="GDScript format strings">$DOCS_URL/tutorials/scripting/gdscript/gdscript_format_string.html</link> @@ -43,30 +45,32 @@ <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if the string begins with the given string. + Returns [code]true[/code] if the string begins with the given [param text]. See also [method ends_with]. </description> </method> <method name="bigrams" qualifiers="const"> <return type="PackedStringArray" /> <description> - Returns an array containing the bigrams (pairs of consecutive letters) of this string. + Returns an array containing the bigrams (pairs of consecutive characters) of this string. [codeblock] - print("Bigrams".bigrams()) # Prints ["Bi", "ig", "gr", "ra", "am", "ms"] + print("Get up!".bigrams()) # Prints ["Ge", "et", "t ", " u", "up", "p!"] [/codeblock] </description> </method> <method name="bin_to_int" qualifiers="const"> <return type="int" /> <description> - Converts a string containing a binary number into an integer. Binary strings can either be prefixed with [code]0b[/code] or not, and they can also start with a [code]-[/code] before the optional prefix. + Converts the string representing a binary number into an [int]. The string may optionally be prefixed with [code]"0b"[/code], and an additional [code]-[/code] prefix for negative numbers. [codeblocks] [gdscript] - print("0b101".bin_to_int()) # Prints "5". - print("101".bin_to_int()) # Prints "5". + print("101".bin_to_int()) # Prints 5 + print("0b101".bin_to_int()) # Prints 5 + print("-0b10".bin_to_int()) # Prints -2 [/gdscript] [csharp] - GD.Print("0b101".BinToInt()); // Prints "5". - GD.Print("101".BinToInt()); // Prints "5". + GD.Print("101".BinToInt()); // Prints 5 + GD.Print("0b101".BinToInt()); // Prints 5 + GD.Print("-0b10".BinToInt()); // Prints -2 [/csharp] [/codeblocks] </description> @@ -87,26 +91,36 @@ <method name="capitalize" qualifiers="const"> <return type="String" /> <description> - Changes the case of some letters. Replaces underscores with spaces, adds spaces before in-word uppercase characters, converts all letters to lowercase, then capitalizes the first letter and every letter following a space character. For [code]capitalize camelCase mixed_with_underscores[/code], it will return [code]Capitalize Camel Case Mixed With Underscores[/code]. + Changes the appearance of the string: replaces underscores ([code]_[/code]) with spaces, adds spaces before uppercase letters in the middle of a word, converts all letters to lowercase, then converts the first one and each one following a space to uppercase. + [codeblocks] + [gdscript] + "move_local_x".capitalize() # Returns "Move Local X" + "sceneFile_path".capitalize() # Returns "Scene File Path" + [/gdscript] + [csharp] + "move_local_x".Capitalize(); // Returns "Move Local X" + "sceneFile_path".Capitalize(); // Returns "Scene File Path" + [/csharp] + [/codeblocks] + [b]Note:[/b] This method not the same as the default appearance of properties in the Inspector dock, as it does not capitalize acronyms ([code]"2D"[/code], [code]"FPS"[/code], [code]"PNG"[/code], etc.) as you may expect. </description> </method> <method name="casecmp_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="String" /> <description> - Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. - [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. - [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. - To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method naturalnocasecmp_to]. + Performs a case-sensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" and "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. + With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code]. + To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method naturalnocasecmp_to]. </description> </method> <method name="chr" qualifiers="static"> <return type="String" /> <param index="0" name="char" type="int" /> <description> - Directly converts an decimal integer to a unicode character. Tables of these characters can be found in various locations, for example [url=https://unicodelookup.com/]here.[/url] + Returns a single Unicode character from the decimal [param char]. You may use [url=https://unicodelookup.com/]unicodelookup.com[/url] or [url=https://www.unicode.org/charts/]unicode.org[/url] as points of reference. [codeblock] - print(String.chr(65)) # Prints "A" + print(String.chr(65)) # Prints "A" print(String.chr(129302)) # Prints "🤖" (robot face emoji) [/codeblock] </description> @@ -115,7 +129,19 @@ <return type="bool" /> <param index="0" name="what" type="String" /> <description> - Returns [code]true[/code] if the string contains the given string. + Returns [code]true[/code] if the string contains [param what]. In GDScript, this corresponds to the [code]in[/code] operator. + [codeblocks] + [gdscript] + print("Node".contains("de")) # Prints true + print("team".contains("I")) # Prints false + print("I" in "team") # Prints false + [/gdscript] + [csharp] + GD.Print("Node".Contains("de")); // Prints true + GD.Print("team".Contains("I")); // Prints false + [/csharp] + [/codeblocks] + If you need to know where [param what] is within the string, use [method find]. </description> </method> <method name="count" qualifiers="const"> @@ -124,7 +150,7 @@ <param index="1" name="from" type="int" default="0" /> <param index="2" name="to" type="int" default="0" /> <description> - Returns the number of occurrences of substring [param what] between [param from] and [param to] positions. If [param from] and [param to] equals 0 the whole string will be used. If only [param to] equals 0 the remained substring will be used. + Returns the number of occurrences of the substring [param what] between [param from] and [param to] positions. If [param to] is 0, the search continues until the end of the string. </description> </method> <method name="countn" qualifiers="const"> @@ -133,7 +159,7 @@ <param index="1" name="from" type="int" default="0" /> <param index="2" name="to" type="int" default="0" /> <description> - Returns the number of occurrences of substring [param what] (ignoring case) between [param from] and [param to] positions. If [param from] and [param to] equals 0 the whole string will be used. If only [param to] equals 0 the remained substring will be used. + Returns the number of occurrences of the substring [param what] between [param from] and [param to] positions, [b]ignoring case[/b]. If [param to] is 0, the search continues until the end of the string. </description> </method> <method name="dedent" qualifiers="const"> @@ -146,7 +172,7 @@ <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if the string ends with the given string. + Returns [code]true[/code] if the string ends with the given [param text]. See also [method begins_with]. </description> </method> <method name="find" qualifiers="const"> @@ -154,17 +180,24 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="0" /> <description> - Returns the index of the [b]first[/b] case-sensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the end of the string. - [b]Note:[/b] If you just want to know whether a string contains a substring, use the [code]in[/code] operator as follows: + Returns the index of the [b]first[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the end of the string. [codeblocks] [gdscript] - print("i" in "team") # Will print `false`. + print("Team".find("I")) # Prints -1 + + print("Potato".find("t")) # Prints 2 + print("Potato".find("t", 3)) # Prints 4 + print("Potato".find("t", 5)) # Prints -1 [/gdscript] [csharp] - // C# has no in operator, but we can use `Contains()`. - GD.Print("team".Contains("i")); // Will print `false`. + GD.Print("Team".Find("I")); // Prints -1 + + GD.Print("Potato".Find("t")); // Prints 2 + GD.print("Potato".Find("t", 3)); // Prints 4 + GD.print("Potato".Find("t", 5)); // Prints -1 [/csharp] [/codeblocks] + [b]Note:[/b] If you just want to know whether the string contains [param what], use [method contains]. In GDScript, you may also use the [code]in[/code] operator. </description> </method> <method name="findn" qualifiers="const"> @@ -172,7 +205,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="0" /> <description> - Returns the index of the [b]first[/b] case-insensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the end of the string. + Returns the index of the [b]first[/b] [b]case-insensitive[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The starting search index can be specified with [param from], continuing to the end of the string. </description> </method> <method name="format" qualifiers="const"> @@ -183,53 +216,64 @@ Formats the string by replacing all occurrences of [param placeholder] with the elements of [param values]. [param values] can be a [Dictionary] or an [Array]. Any underscores in [param placeholder] will be replaced with the corresponding keys in advance. Array elements use their index as keys. [codeblock] - # Prints: Waiting for Godot is a play by Samuel Beckett, and Godot Engine is named after it. + # Prints "Waiting for Godot is a play by Samuel Beckett, and Godot Engine is named after it." var use_array_values = "Waiting for {0} is a play by {1}, and {0} Engine is named after it." print(use_array_values.format(["Godot", "Samuel Beckett"])) - # Prints: User 42 is Godot. + # Prints "User 42 is Godot." print("User {id} is {name}.".format({"id": 42, "name": "Godot"})) [/codeblock] - Some additional handling is performed when [param values] is an array. If [param placeholder] does not contain an underscore, the elements of the array will be used to replace one occurrence of the placeholder in turn; If an array element is another 2-element array, it'll be interpreted as a key-value pair. + Some additional handling is performed when [param values] is an [Array]. If [param placeholder] does not contain an underscore, the elements of the [param values] array will be used to replace one occurrence of the placeholder in order; If an element of [param values] is another 2-element array, it'll be interpreted as a key-value pair. [codeblock] - # Prints: User 42 is Godot. + # Prints "User 42 is Godot." print("User {} is {}.".format([42, "Godot"], "{}")) print("User {id} is {name}.".format([["id", 42], ["name", "Godot"]])) [/codeblock] + See also the [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_format_string.html]GDScript format string[/url] tutorial. </description> </method> <method name="get_base_dir" qualifiers="const"> <return type="String" /> <description> If the string is a valid file path, returns the base directory name. + [codeblock] + var dir_path = "/path/to/file.txt".get_basename() # dir_path is "/path/to" + [/codeblock] </description> </method> <method name="get_basename" qualifiers="const"> <return type="String" /> <description> - If the string is a valid file path, returns the full file path without the extension. + If the string is a valid file path, returns the full file path, without the extension. + [codeblock] + var base = "/path/to/file.txt".get_basename() # base is "/path/to/file" + [/codeblock] </description> </method> <method name="get_extension" qualifiers="const"> <return type="String" /> <description> - Returns the extension without the leading period character ([code].[/code]) if the string is a valid file name or path. If the string does not contain an extension, returns an empty string instead. + If the string is a valid file name or path, returns the file extension without the leading period ([code].[/code]). Otherwise, returns an empty string. [codeblock] - print("/path/to/file.txt".get_extension()) # "txt" - print("file.txt".get_extension()) # "txt" - print("file.sample.txt".get_extension()) # "txt" - print(".txt".get_extension()) # "txt" - print("file.txt.".get_extension()) # "" (empty string) - print("file.txt..".get_extension()) # "" (empty string) - print("txt".get_extension()) # "" (empty string) - print("".get_extension()) # "" (empty string) + var a = "/path/to/file.txt".get_extension() # a is "txt" + var b = "cool.txt".get_extension() # b is "txt" + var c = "cool.font.tres".get_extension() # c is "tres" + var d = ".pack1".get_extension() # d is "pack1" + + var e = "file.txt.".get_extension() # e is "" + var f = "file.txt..".get_extension() # f is "" + var g = "txt".get_extension() # g is "" + var h = "".get_extension() # h is "" [/codeblock] </description> </method> <method name="get_file" qualifiers="const"> <return type="String" /> <description> - If the string is a valid file path, returns the filename. + If the string is a valid file path, returns the file name, including the extension. + [codeblock] + var file = "/path/to/icon.png".get_file() # file is "icon.png" + [/codeblock] </description> </method> <method name="get_slice" qualifiers="const"> @@ -237,11 +281,11 @@ <param index="0" name="delimiter" type="String" /> <param index="1" name="slice" type="int" /> <description> - Splits a string using a [param delimiter] and returns a substring at index [param slice]. Returns an empty string if the index doesn't exist. - This is a more performant alternative to [method split] for cases when you need only one element from the array at a fixed index. + Splits the string using a [param delimiter] and returns the substring at index [param slice]. Returns an empty string if the [param slice] does not exist. + This is faster than [method split], if you only need one substring. [b]Example:[/b] [codeblock] - print("i/am/example/string".get_slice("/", 2)) # Prints 'example'. + print("i/am/example/hi".get_slice("/", 2)) # Prints "example" [/codeblock] </description> </method> @@ -249,7 +293,7 @@ <return type="int" /> <param index="0" name="delimiter" type="String" /> <description> - Splits a string using a [param delimiter] and returns a number of slices. + Returns the total number of slices when the string is split with the given [param delimiter] (see [method split]). </description> </method> <method name="get_slicec" qualifiers="const"> @@ -257,29 +301,29 @@ <param index="0" name="delimiter" type="int" /> <param index="1" name="slice" type="int" /> <description> - Splits a string using a Unicode character with code [param delimiter] and returns a substring at index [param slice]. Returns an empty string if the index doesn't exist. - This is a more performant alternative to [method split] for cases when you need only one element from the array at a fixed index. + Splits the string using a Unicode character with code [param delimiter] and returns the substring at index [param slice]. Returns an empty string if the [param slice] does not exist. + This is faster than [method split], if you only need one substring. </description> </method> <method name="hash" qualifiers="const"> <return type="int" /> <description> Returns the 32-bit hash value representing the string's contents. - [b]Note:[/b] [String]s with equal content will always produce identical hash values. However, the reverse is not true. Returning identical hash values does [i]not[/i] imply the strings are equal, because different strings can have identical hash values due to hash collisions. + [b]Note:[/b] Strings with equal hash values are [i]not[/i] guaranteed to be the same, as a result of hash collisions. On the countrary, strings with different hash values are guaranteed to be different. </description> </method> <method name="hex_to_int" qualifiers="const"> <return type="int" /> <description> - Converts a string containing a hexadecimal number into an integer. Hexadecimal strings can either be prefixed with [code]0x[/code] or not, and they can also start with a [code]-[/code] before the optional prefix. + Converts the string representing a hexadecimal number into an [int]. The string may be optionally prefixed with [code]"0x"[/code], and an additional [code]-[/code] prefix for negative numbers. [codeblocks] [gdscript] - print("0xff".hex_to_int()) # Prints "255". - print("ab".hex_to_int()) # Prints "171". + print("0xff".hex_to_int()) # Prints 255 + print("ab".hex_to_int()) # Prints 171 [/gdscript] [csharp] - GD.Print("0xff".HexToInt()); // Prints "255". - GD.Print("ab".HexToInt()); // Prints "171". + GD.Print("0xff".HexToInt()); // Prints 255 + GD.Print("ab".HexToInt()); // Prints 171 [/csharp] [/codeblocks] </description> @@ -288,17 +332,16 @@ <return type="String" /> <param index="0" name="size" type="int" /> <description> - Converts an integer representing a number of bytes into a human-readable form. - Note that this output is in [url=https://en.wikipedia.org/wiki/Binary_prefix#IEC_prefixes]IEC prefix format[/url], and includes [code]B[/code], [code]KiB[/code], [code]MiB[/code], [code]GiB[/code], [code]TiB[/code], [code]PiB[/code], and [code]EiB[/code]. + Converts [param size] which represents a number of bytes into a human-readable form. + The result is in [url=https://en.wikipedia.org/wiki/Binary_prefix#IEC_prefixes]IEC prefix format[/url], which may end in either [code]"B"[/code], [code]"KiB"[/code], [code]"MiB"[/code], [code]"GiB"[/code], [code]"TiB"[/code], [code]"PiB"[/code], or [code]"EiB"[/code]. </description> </method> <method name="indent" qualifiers="const"> <return type="String" /> <param index="0" name="prefix" type="String" /> <description> - Returns a copy of the string with lines indented with [param prefix]. - For example, the string can be indented with two tabs using [code]"\t\t"[/code], or four spaces using [code]" "[/code]. The prefix can be any string so it can also be used to comment out strings with e.g. [code]"# "[/code]. See also [method dedent] to remove indentation. - [b]Note:[/b] Empty lines are kept empty. + Indents every line of the string with the given [param prefix]. Empty lines are not indented. See also [method dedent] to remove indentation. + For example, the string can be indented with two tabulations using [code]"\t\t"[/code], or four spaces using [code]" "[/code]. </description> </method> <method name="insert" qualifiers="const"> @@ -306,57 +349,65 @@ <param index="0" name="position" type="int" /> <param index="1" name="what" type="String" /> <description> - Returns a copy of the string with the substring [param what] inserted at the given [param position]. + Inserts [param what] at the given [param position] in the string. </description> </method> <method name="is_absolute_path" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the string is a path to a file or directory and its starting point is explicitly defined. This includes [code]res://[/code], [code]user://[/code], [code]C:\[/code], [code]/[/code], etc. + Returns [code]true[/code] if the string is a path to a file or directory, and its starting point is explicitly defined. This method is the opposite of [method is_relative_path]. + This includes all paths starting with [code]"res://"[/code], [code]"user://"[/code], [code]"C:\"[/code], [code]"/"[/code], etc. </description> </method> <method name="is_empty" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the length of the string equals [code]0[/code]. + Returns [code]true[/code] if the string's length is [code]0[/code] ([code]""[/code]). See also [method length]. </description> </method> <method name="is_relative_path" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the string is a path to a file or directory and its starting point is implicitly defined within the context it is being used. The starting point may refer to the current directory ([code]./[/code]), or the current [Node]. + Returns [code]true[/code] if the string is a path, and its starting point is dependent on context. The path could begin from the current directory, or the current [Node] (if the string is derived from a [NodePath]), and may sometimes be prefixed with [code]"./"[/code]. This method is the opposite of [method is_absolute_path]. </description> </method> <method name="is_subsequence_of" qualifiers="const"> <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if this string is a subsequence of the given string. + Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order. + [codeblock] + var text = "Wow, incredible!" + + print("inedible".is_subsequence_of(text)) # Prints true + print("Word!".is_subsequence_of(text)) # Prints true + print("Window".is_subsequence_of(text)) # Prints false + print("".is_subsequence_of(text)) # Prints true + [/codeblock] </description> </method> <method name="is_subsequence_ofn" qualifiers="const"> <return type="bool" /> <param index="0" name="text" type="String" /> <description> - Returns [code]true[/code] if this string is a subsequence of the given string, without considering case. + Returns [code]true[/code] if all characters of this string can be found in [param text] in their original order, [b]ignoring case[/b]. </description> </method> <method name="is_valid_filename" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string is free from characters that aren't allowed in file names, those being: - [code]: / \ ? * " | % < >[/code] + Returns [code]true[/code] if this string does not contain characters that are not allowed in file names ([code]:[/code] [code]/[/code] [code]\[/code] [code]?[/code] [code]*[/code] [code]"[/code] [code]|[/code] [code]%[/code] [code]<[/code] [code]>[/code]). </description> </method> <method name="is_valid_float" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains a valid float. This is inclusive of integers, and also supports exponents: + Returns [code]true[/code] if this string represents a valid floating-point number. A valid float may contain only digits, one decimal point ([code].[/code]), and the exponent letter ([code]e[/code]). It may also be prefixed with a positive ([code]+[/code]) or negative ([code]-[/code]) sign. Any valid integer is also a valid float (see [method is_valid_int]). See also [method to_float]. [codeblock] - print("1.7".is_valid_float()) # Prints "true" - print("24".is_valid_float()) # Prints "true" - print("7e3".is_valid_float()) # Prints "true" - print("Hello".is_valid_float()) # Prints "false" + print("1.7".is_valid_float()) # Prints true + print("24".is_valid_float()) # Prints true + print("7e3".is_valid_float()) # Prints true + print("Hello".is_valid_float()) # Prints false [/codeblock] </description> </method> @@ -364,57 +415,73 @@ <return type="bool" /> <param index="0" name="with_prefix" type="bool" default="false" /> <description> - Returns [code]true[/code] if this string contains a valid hexadecimal number. If [param with_prefix] is [code]true[/code], then a validity of the hexadecimal number is determined by the [code]0x[/code] prefix, for example: [code]0xDEADC0DE[/code]. + Returns [code]true[/code] if this string is a valid hexadecimal number. A valid hexadecimal number only contains digits or letters [code]A[/code] to [code]F[/code] (either uppercase or lowercase), and may be prefixed with a positive ([code]+[/code]) or negative ([code]-[/code]) sign. + If [param with_prefix] is [code]true[/code], the hexadecimal number needs to prefixed by [code]"0x"[/code] to be considered valid. + [codeblock] + print("A08E".is_valid_hex_number()) # Prints true + print("-AbCdEf".is_valid_hex_number()) # Prints true + print("2.5".is_valid_hex_number()) # Prints false + + print("0xDEADC0DE".is_valid_hex_number(true)) # Prints true + [/codeblock] </description> </method> <method name="is_valid_html_color" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains a valid color in hexadecimal HTML notation. Other HTML notations such as named colors or [code]hsl()[/code] colors aren't considered valid by this method and will return [code]false[/code]. + Returns [code]true[/code] if this string is a valid color in hexadecimal HTML notation. The string must be a hexadecimal value (see [method is_valid_hex_number]) of either 3, 4, 6 or 8 digits, and may be prefixed by a hash sign ([code]#[/code]). Other HTML notations for colors, such as names or [code]hsl()[/code], are not considered valid. See also [method Color.html]. </description> </method> <method name="is_valid_identifier" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]) and the first character may not be a digit. + Returns [code]true[/code] if this string is a valid identifier. A valid identifier may contain only letters, digits and underscores ([code]_[/code]), and the first character may not be a digit. [codeblock] - print("good_ident_1".is_valid_identifier()) # Prints "true" - print("1st_bad_ident".is_valid_identifier()) # Prints "false" - print("bad_ident_#2".is_valid_identifier()) # Prints "false" + print("node_2d".is_valid_identifier()) # Prints true + print("TYPE_FLOAT".is_valid_identifier()) # Prints true + print("1st_method".is_valid_identifier()) # Prints false + print("MyMethod#2".is_valid_identifier()) # Prints false [/codeblock] </description> </method> <method name="is_valid_int" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains a valid integer. + Returns [code]true[/code] if this string represents a valid integer. A valid integer only contains digits, and may be prefixed with a positive ([code]+[/code]) or negative ([code]-[/code]) sign. See also [method to_int]. [codeblock] - print("7".is_valid_int()) # Prints "true" - print("14.6".is_valid_int()) # Prints "false" - print("L".is_valid_int()) # Prints "false" - print("+3".is_valid_int()) # Prints "true" - print("-12".is_valid_int()) # Prints "true" + print("7".is_valid_int()) # Prints true + print("1.65".is_valid_int()) # Prints false + print("Hi".is_valid_int()) # Prints false + print("+3".is_valid_int()) # Prints true + print("-12".is_valid_int()) # Prints true [/codeblock] </description> </method> <method name="is_valid_ip_address" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if this string contains only a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]0.0.0.0[/code] as valid. + Returns [code]true[/code] if this string represents a well-formatted IPv4 or IPv6 address. This method considers [url=https://en.wikipedia.org/wiki/Reserved_IP_addresses]reserved IP addresses[/url] such as [code]"0.0.0.0"[/code] and [code]"ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"[/code] as valid. </description> </method> <method name="join" qualifiers="const"> <return type="String" /> <param index="0" name="parts" type="PackedStringArray" /> <description> - Returns a [String] which is the concatenation of the [param parts]. The separator between elements is the string providing this method. + Returns the concatenation of [param parts]' elements, with each element separated by the string calling this method. This method is the opposite of [method split]. [b]Example:[/b] [codeblocks] [gdscript] - print(", ".join(["One", "Two", "Three", "Four"])) + var fruits = ["Apple", "Orange", "Pear", "Kiwi"] + + print(", ".join(fruits)) # Prints "Apple, Orange, Pear, Kiwi" + print("---".join(fruits)) # Prints "Apple---Orange---Pear---Kiwi" [/gdscript] [csharp] - GD.Print(String.Join(",", new string[] {"One", "Two", "Three", "Four"})); + var fruits = new string[] {"Apple", "Orange", "Pear", "Kiwi"}; + + // In C#, this method is static. + GD.Print(string.Join(", ", fruits); // Prints "Apple, Orange, Pear, Kiwi" + GD.Print(string.Join("---", fruits)); // Prints "Apple---Orange---Pear---Kiwi" [/csharp] [/codeblocks] </description> @@ -422,25 +489,24 @@ <method name="json_escape" qualifiers="const"> <return type="String" /> <description> - Returns a copy of the string with special characters escaped using the JSON standard. + Returns a copy of the string with special characters escaped using the JSON standard. Because it closely matches the C standard, it is possible to use [method c_unescape] to unescape the string, if necessary. </description> </method> <method name="left" qualifiers="const"> <return type="String" /> <param index="0" name="length" type="int" /> <description> - Returns a number of characters from the left of the string. If negative [param length] is used, the characters are counted downwards from [String]'s length. - [b]Example:[/b] + Returns the first [param length] characters from the beginning of the string. If [param length] is negative, strips the last [param length] characters from the string's end. [codeblock] - print("sample text".left(3)) #prints "sam" - print("sample text".left(-3)) #prints "sample t" + print("Hello World!".left(3)) # Prints "Hel" + print("Hello World!".left(-4)) # Prints "Hello Wo" [/codeblock] </description> </method> <method name="length" qualifiers="const"> <return type="int" /> <description> - Returns the number of characters in the string. + Returns the number of characters in the string. Empty strings ([code]""[/code]) always return [code]0[/code]. See also [method is_empty]. </description> </method> <method name="lpad" qualifiers="const"> @@ -448,62 +514,60 @@ <param index="0" name="min_length" type="int" /> <param index="1" name="character" type="String" default="" "" /> <description> - Formats a string to be at least [param min_length] long by adding [param character]s to the left of the string. + Formats the string to be at least [param min_length] long by adding [param character]s to the left of the string, if necessary. See also [method rpad]. </description> </method> <method name="lstrip" qualifiers="const"> <return type="String" /> <param index="0" name="chars" type="String" /> <description> - Returns a copy of the string with characters removed from the left. The [param chars] argument is a string specifying the set of characters to be removed. - [b]Note:[/b] The [param chars] is not a prefix. See [method trim_prefix] method that will remove a single prefix string rather than a set of characters. + Removes a set of characters defined in [param chars] from the string's beginning. See also [method rstrip]. + [b]Note:[/b] [param chars] is not a prefix. Use [method trim_prefix] to remove a single prefix, rather than a set of characters. </description> </method> <method name="match" qualifiers="const"> <return type="bool" /> <param index="0" name="expr" type="String" /> <description> - Does a simple case-sensitive expression match, where [code]"*"[/code] matches zero or more arbitrary characters and [code]"?"[/code] matches any single character except a period ([code]"."[/code]). An empty string or empty expression always evaluates to [code]false[/code]. + Does a simple expression match, where [code]*[/code] matches zero or more arbitrary characters and [code]?[/code] matches any single character except a period ([code].[/code]). An empty string or empty expression always evaluates to [code]false[/code]. </description> </method> <method name="matchn" qualifiers="const"> <return type="bool" /> <param index="0" name="expr" type="String" /> <description> - Does a simple case-insensitive expression match, where [code]"*"[/code] matches zero or more arbitrary characters and [code]"?"[/code] matches any single character except a period ([code]"."[/code]). An empty string or empty expression always evaluates to [code]false[/code]. + Does a simple [b]case-insensitive[/b] expression match, where [code]*[/code] matches zero or more arbitrary characters and [code]?[/code] matches any single character except a period ([code].[/code]). An empty string or empty expression always evaluates to [code]false[/code]. </description> </method> <method name="md5_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Returns the MD5 hash of the string as an array of bytes. + Returns the [url=https://en.wikipedia.org/wiki/MD5]MD5 hash[/url] of the string as a [PackedByteArray]. </description> </method> <method name="md5_text" qualifiers="const"> <return type="String" /> <description> - Returns the MD5 hash of the string as a string. + Returns the [url=https://en.wikipedia.org/wiki/MD5]MD5 hash[/url] of the string as another [String]. </description> </method> <method name="naturalnocasecmp_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="String" /> <description> - Performs a case-insensitive [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters will be converted to uppercase during the comparison. - When used for sorting, natural order comparison will order suites of numbers as expected by most people. If you sort the numbers from 1 to 10 using natural order, you will get [code][1, 2, 3, ...][/code] instead of [code][1, 10, 2, 3, ...][/code]. - [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. - [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. - To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method casecmp_to]. + Performs a [b]case-insensitive[/b], [i]natural order[/i] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters are converted to uppercase for the comparison. + When used for sorting, natural order comparison orders sequences of numbers by the combined value of each digit as is often expected, instead of the single digit's value. A sorted sequence of numbered strings will be [code]["1", "2", "3", ...][/code], not [code]["1", "10", "2", "3", ...][/code]. + With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code]. + To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method nocasecmp_to] and [method casecmp_to]. </description> </method> <method name="nocasecmp_to" qualifiers="const"> <return type="int" /> <param index="0" name="to" type="String" /> <description> - Performs a case-insensitive comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters will be converted to uppercase during the comparison. - [b]Behavior with different string lengths:[/b] Returns [code]1[/code] if the "base" string is longer than the [param to] string or [code]-1[/code] if the "base" string is shorter than the [param to] string. Keep in mind this length is determined by the number of Unicode codepoints, [i]not[/i] the actual visible characters. - [b]Behavior with empty strings:[/b] Returns [code]-1[/code] if the "base" string is empty, [code]1[/code] if the [param to] string is empty or [code]0[/code] if both strings are empty. - To get a boolean result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to] and [method naturalnocasecmp_to]. + Performs a [b]case-insensitive[/b] comparison to another string. Returns [code]-1[/code] if less than, [code]1[/code] if greater than, or [code]0[/code] if equal. "Less than" or "greater than" are determined by the [url=https://en.wikipedia.org/wiki/List_of_Unicode_characters]Unicode code points[/url] of each string, which roughly matches the alphabetical order. Internally, lowercase characters are converted to uppercase for the comparison. + With different string lengths, returns [code]1[/code] if this string is longer than the [param to] string, or [code]-1[/code] if shorter. Note that the length of empty strings is [i]always[/i] [code]0[/code]. + To get a [bool] result from a string comparison, use the [code]==[/code] operator instead. See also [method casecmp_to] and [method naturalnocasecmp_to]. </description> </method> <method name="num" qualifiers="static"> @@ -511,20 +575,22 @@ <param index="0" name="number" type="float" /> <param index="1" name="decimals" type="int" default="-1" /> <description> - Converts a [float] to a string representation of a decimal number. - The number of decimal places can be specified with [param decimals]. If [param decimals] is [code]-1[/code] (default), decimal places will be automatically adjusted so that the string representation has 14 significant digits (counting both digits to the left and the right of the decimal point). - Trailing zeros are not included in the string. The last digit will be rounded and not truncated. + Converts a [float] to a string representation of a decimal number, with the number of decimal places specified in [param decimals]. + If [param decimals] is [code]-1[/code] as by default, the string representation may only have up to 14 significant digits, with digits before the decimal point having priority over digits after. + Trailing zeros are not included in the string. The last digit is rounded, not truncated. [b]Example:[/b] [codeblock] - String.num(3.141593) # "3.141593" - String.num(3.141593, 3) # "3.142" - String.num(3.14159300) # "3.141593", no trailing zeros. - # Last digit will be rounded up here, which reduces total digit count since - # trailing zeros are removed: - String.num(42.129999, 5) # "42.13" - # If `decimals` is not specified, the total number of significant digits is 14: - String.num(-0.0000012345432123454321) # "-0.00000123454321" - String.num(-10000.0000012345432123454321) # "-10000.0000012345" + String.num(3.141593) # Returns "3.141593" + String.num(3.141593, 3) # Returns "3.142" + String.num(3.14159300) # Returns "3.141593" + + # Here, the last digit will be rounded up, + # which reduces the total digit count, since trailing zeros are removed: + String.num(42.129999, 5) # Returns "42.13" + + # If `decimals` is not specified, the maximum number of significant digits is 14: + String.num(-0.0000012345432123454321) # Returns "-0.00000123454321" + String.num(-10000.0000012345432123454321) # Returns "-10000.0000012345" [/codeblock] </description> </method> @@ -534,13 +600,31 @@ <param index="1" name="base" type="int" default="10" /> <param index="2" name="capitalize_hex" type="bool" default="false" /> <description> - Converts a signed [int] to a string representation of a number. + Converts the given [param number] to a string representation, with the given [param base]. + By default, [param base] is set to decimal ([code]10[/code]). Other common bases in programming include binary ([code]2[/code]), [url=https://en.wikipedia.org/wiki/Octal]octal[/url] ([code]8[/code]), hexadecimal ([code]16[/code]). + If [param capitalize_hex] is [code]true[/code], digits higher than 9 are represented in uppercase. </description> </method> <method name="num_scientific" qualifiers="static"> <return type="String" /> <param index="0" name="number" type="float" /> <description> + Converts the given [param number] to a string representation, in scientific notation. + [codeblocks] + [gdscript] + var n = -5.2e8 + print(n) # Prints -520000000 + print(String.NumScientific(n)) # Prints -5.2e+08 + [/gdscript] + [csharp] + // This method is not implemented in C#. + // Use `string.ToString()` with "e" to achieve similar results. + var n = -5.2e8f; + GD.Print(n); // Prints -520000000 + GD.Print(n.ToString("e1")); // Prints -5.2e+008 + [/csharp] + [/codeblocks] + [b]Note:[/b] In C#, this method is not implemented. To achieve similar results, see C#'s [url=https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings]Standard numeric format strings[/url] </description> </method> <method name="num_uint64" qualifiers="static"> @@ -549,35 +633,38 @@ <param index="1" name="base" type="int" default="10" /> <param index="2" name="capitalize_hex" type="bool" default="false" /> <description> - Converts a unsigned [int] to a string representation of a number. + Converts the given unsigned [int] to a string representation, with the given [param base]. + By default, [param base] is set to decimal ([code]10[/code]). Other common bases in programming include binary ([code]2[/code]), [url=https://en.wikipedia.org/wiki/Octal]octal[/url] ([code]8[/code]), hexadecimal ([code]16[/code]). + If [param capitalize_hex] is [code]true[/code], digits higher than 9 are represented in uppercase. </description> </method> <method name="pad_decimals" qualifiers="const"> <return type="String" /> <param index="0" name="digits" type="int" /> <description> - Formats a number to have an exact number of [param digits] after the decimal point. + Formats the string representing a number to have an exact number of [param digits] [i]after[/i] the decimal point. </description> </method> <method name="pad_zeros" qualifiers="const"> <return type="String" /> <param index="0" name="digits" type="int" /> <description> - Formats a number to have an exact number of [param digits] before the decimal point. + Formats the string representing a number to have an exact number of [param digits] [i]before[/i] the decimal point. </description> </method> <method name="path_join" qualifiers="const"> <return type="String" /> <param index="0" name="file" type="String" /> <description> - If the string is a path, this concatenates [param file] at the end of the string as a subpath. E.g. [code]"this/is".path_join("path") == "this/is/path"[/code]. + Concatenates [param file] at the end of the string as a subpath, adding [code]/[/code] if necessary. + [b]Example:[/b] [code]"this/is".path_join("path") == "this/is/path"[/code]. </description> </method> <method name="repeat" qualifiers="const"> <return type="String" /> <param index="0" name="count" type="int" /> <description> - Returns original string repeated a number of times. The number of repetitions is given by the argument. + Repeats this string a number of times. [param count] needs to be greater than [code]0[/code]. Otherwise, returns an empty string. </description> </method> <method name="replace" qualifiers="const"> @@ -585,7 +672,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="forwhat" type="String" /> <description> - Replaces occurrences of a case-sensitive substring with the given one inside the string. + Replaces all occurrences of [param what] inside the string with the given [param forwhat]. </description> </method> <method name="replacen" qualifiers="const"> @@ -593,7 +680,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="forwhat" type="String" /> <description> - Replaces occurrences of a case-insensitive substring with the given one inside the string. + Replaces all [b]case-insensitive[/b] occurrences of [param what] inside the string with the given [param forwhat]. </description> </method> <method name="rfind" qualifiers="const"> @@ -601,7 +688,7 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="-1" /> <description> - Returns the index of the [b]last[/b] case-sensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the beginning of the string. + Returns the index of the [b]last[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The search's start can be specified with [param from], continuing to the beginning of the string. This method is the reverse of [method find]. </description> </method> <method name="rfindn" qualifiers="const"> @@ -609,18 +696,17 @@ <param index="0" name="what" type="String" /> <param index="1" name="from" type="int" default="-1" /> <description> - Returns the index of the [b]last[/b] case-insensitive occurrence of the specified string in this instance, or [code]-1[/code]. Optionally, the starting search index can be specified, continuing to the beginning of the string. + Returns the index of the [b]last[/b] [b]case-insensitive[/b] occurrence of [param what] in this string, or [code]-1[/code] if there are none. The starting search index can be specified with [param from], continuing to the beginning of the string. This method is the reverse of [method findn]. </description> </method> <method name="right" qualifiers="const"> <return type="String" /> <param index="0" name="length" type="int" /> <description> - Returns a number of characters from the right of the string. If negative [param length] is used, the characters are counted downwards from [String]'s length. - [b]Example:[/b] + Returns the last [param length] characters from the end of the string. If [param length] is negative, strips the first [param length] characters from the string's beginning. [codeblock] - print("sample text".right(3)) #prints "ext" - print("sample text".right(-3)) #prints "ple text" + print("Hello World!".right(3)) # Prints "ld!" + print("Hello World!".right(-4)) # Prints "o World!" [/codeblock] </description> </method> @@ -629,7 +715,7 @@ <param index="0" name="min_length" type="int" /> <param index="1" name="character" type="String" default="" "" /> <description> - Formats a string to be at least [param min_length] long by adding [param character]s to the right of the string. + Formats the string to be at least [param min_length] long, by adding [param character]s to the right of the string, if necessary. See also [method lpad]. </description> </method> <method name="rsplit" qualifiers="const"> @@ -638,21 +724,21 @@ <param index="1" name="allow_empty" type="bool" default="true" /> <param index="2" name="maxsplit" type="int" default="0" /> <description> - Splits the string by a [param delimiter] string and returns an array of the substrings, starting from right. If [param delimiter] is an empty string, each substring will be a single character. - The splits in the returned array are sorted in the same order as the original string, from left to right. - If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. - If [param maxsplit] is specified, it defines the number of splits to do from the right up to [param maxsplit]. The default value of 0 means that all items are split, thus giving the same result as [method split]. + Splits the string using a [param delimiter] and returns an array of the substrings, starting from the end of the string. The splits in the returned array appear in the same order as the original string. If [param delimiter] is an empty string, each substring will be a single character. + If [param allow_empty] is [code]false[/code], empty strings between adjacent delimiters are excluded from the array. + If [param maxsplit] is greater than [code]0[/code], the number of splits may not exceed [param maxsplit]. By default, the entire string is split, which is mostly identical to [method split]. [b]Example:[/b] [codeblocks] [gdscript] var some_string = "One,Two,Three,Four" var some_array = some_string.rsplit(",", true, 1) + print(some_array.size()) # Prints 2 - print(some_array[0]) # Prints "One,Two,Three" - print(some_array[1]) # Prints "Four" + print(some_array[0]) # Prints "One,Two,Three" + print(some_array[1]) # Prints "Four" [/gdscript] [csharp] - // There is no Rsplit. + // In C#, there is no String.RSplit() method. [/csharp] [/codeblocks] </description> @@ -661,51 +747,55 @@ <return type="String" /> <param index="0" name="chars" type="String" /> <description> - Returns a copy of the string with characters removed from the right. The [param chars] argument is a string specifying the set of characters to be removed. - [b]Note:[/b] The [param chars] is not a suffix. See [method trim_suffix] method that will remove a single suffix string rather than a set of characters. + Removes a set of characters defined in [param chars] from the string's end. See also [method lstrip]. + [b]Note:[/b] [param chars] is not a suffix. Use [method trim_suffix] to remove a single suffix, rather than a set of characters. </description> </method> <method name="sha1_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Returns the SHA-1 hash of the string as an array of bytes. + Returns the [url=https://en.wikipedia.org/wiki/SHA-1]SHA-1[/url] hash of the string as a [PackedByteArray]. </description> </method> <method name="sha1_text" qualifiers="const"> <return type="String" /> <description> - Returns the SHA-1 hash of the string as a string. + Returns the [url=https://en.wikipedia.org/wiki/SHA-1]SHA-1[/url] hash of the string as another [String]. </description> </method> <method name="sha256_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Returns the SHA-256 hash of the string as an array of bytes. + Returns the [url=https://en.wikipedia.org/wiki/SHA-2]SHA-256[/url] hash of the string as a [PackedByteArray]. </description> </method> <method name="sha256_text" qualifiers="const"> <return type="String" /> <description> - Returns the SHA-256 hash of the string as a string. + Returns the [url=https://en.wikipedia.org/wiki/SHA-2]SHA-256[/url] hash of the string as another [String]. </description> </method> <method name="similarity" qualifiers="const"> <return type="float" /> <param index="0" name="text" type="String" /> <description> - Returns the similarity index ([url=https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient]Sorensen-Dice coefficient[/url]) of this string compared to another. A result of 1.0 means totally similar, while 0.0 means totally dissimilar. + Returns the similarity index ([url=https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient]Sorensen-Dice coefficient[/url]) of this string compared to another. A result of [code]1.0[/code] means totally similar, while [code]0.0[/code] means totally dissimilar. [codeblock] - print("ABC123".similarity("ABC123")) # Prints "1" - print("ABC123".similarity("XYZ456")) # Prints "0" - print("ABC123".similarity("123ABC")) # Prints "0.8" - print("ABC123".similarity("abc123")) # Prints "0.4" + print("ABC123".similarity("ABC123")) # Prints 1.0 + print("ABC123".similarity("XYZ456")) # Prints 0.0 + print("ABC123".similarity("123ABC")) # Prints 0.8 + print("ABC123".similarity("abc123")) # Prints 0.4 [/codeblock] </description> </method> <method name="simplify_path" qualifiers="const"> <return type="String" /> <description> - Returns a simplified canonical path. + If the string is a valid file path, converts the string into a canonical path. This is the shortest possible path, without [code]"./"[/code], and all the unnecessary [code]".."[/code] and [code]"/"[/code]. + [codeblock] + var simple_path = "./path/to///../file".simplify_path() + print(simple_path) # Prints "path/file" + [/codeblock] </description> </method> <method name="split" qualifiers="const"> @@ -714,27 +804,29 @@ <param index="1" name="allow_empty" type="bool" default="true" /> <param index="2" name="maxsplit" type="int" default="0" /> <description> - Splits the string by a [param delimiter] string and returns an array of the substrings. The [param delimiter] can be of any length. If [param delimiter] is an empty string, each substring will be a single character. - If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. - If [param maxsplit] is specified, it defines the number of splits to do from the left up to [param maxsplit]. The default value of [code]0[/code] means that all items are split. - If you need only one element from the array at a specific index, [method get_slice] is a more performant option. + Splits the string using a [param delimiter] and returns an array of the substrings. If [param delimiter] is an empty string, each substring will be a single character. This method is the opposite of [method join]. + If [param allow_empty] is [code]false[/code], empty strings between adjacent delimiters are excluded from the array. + If [param maxsplit] is greater than [code]0[/code], the number of splits may not exceed [param maxsplit]. By default, the entire string is split. [b]Example:[/b] [codeblocks] [gdscript] - var some_string = "One,Two,Three,Four" - var some_array = some_string.split(",", true, 1) - print(some_array.size()) # Prints 2 - print(some_array[0]) # Prints "Four" - print(some_array[1]) # Prints "Three,Two,One" + var some_array = "One,Two,Three,Four".split(",", true, 2) + + print(some_array.size()) # Prints 3 + print(some_array[0]) # Prints "One" + print(some_array[1]) # Prints "Two" + print(some_array[2]) # Prints "Three,Four" [/gdscript] [csharp] - var someString = "One,Two,Three,Four"; - var someArray = someString.Split(",", true); // This is as close as it gets to Godots API. - GD.Print(someArray[0]); // Prints "Four" - GD.Print(someArray[1]); // Prints "Three,Two,One" + // C#'s `Split()` does not support the `maxsplit` parameter. + var someArray = "One,Two,Three".Split(","); + + GD.Print(someArray[0]); // Prints "One" + GD.Print(someArray[1]); // Prints "Two" + GD.Print(someArray[2]); // Prints "Three" [/csharp] [/codeblocks] - If you need to split strings with more complex rules, use the [RegEx] class instead. + [b]Note:[/b] If you only need one substring from the array, consider using [method get_slice] which is faster. If you need to split strings with more complex rules, use the [RegEx] class instead. </description> </method> <method name="split_floats" qualifiers="const"> @@ -742,9 +834,13 @@ <param index="0" name="delimiter" type="String" /> <param index="1" name="allow_empty" type="bool" default="true" /> <description> - Splits the string in floats by using a delimiter string and returns an array of the substrings. - For example, [code]"1,2.5,3"[/code] will return [code][1,2.5,3][/code] if split by [code]","[/code]. - If [param allow_empty] is [code]true[/code], and there are two adjacent delimiters in the string, it will add an empty string to the array of substrings at this position. + Splits the string into floats by using a [param delimiter] and returns a [PackedFloat64Array]. + If [param allow_empty] is [code]false[/code], empty or invalid [float] conversions between adjacent delimiters are excluded. + [codeblock] + var a = "1,2,4.5".split_floats(",") # a is [1.0, 2.0, 4.5] + var c = "1| ||4.5".split_floats("|") # c is [1.0, 0.0, 0.0, 4.5] + var b = "1| ||4.5".split_floats("|", false) # b is [1.0, 4.5] + [/codeblock] </description> </method> <method name="strip_edges" qualifiers="const"> @@ -752,13 +848,14 @@ <param index="0" name="left" type="bool" default="true" /> <param index="1" name="right" type="bool" default="true" /> <description> - Returns a copy of the string stripped of any non-printable character (including tabulations, spaces and line breaks) at the beginning and the end. The optional arguments are used to toggle stripping on the left and right edges respectively. + Strips all non-printable characters from the beginning and the end of the string. These include spaces, tabulations ([code]\t[/code]), and newlines ([code]\n[/code] [code]\r[/code]). + If [param left] is [code]false[/code], ignores the string's beginning. Likewise, if [param right] is [code]false[/code], ignores the string's end. </description> </method> <method name="strip_escapes" qualifiers="const"> <return type="String" /> <description> - Returns a copy of the string stripped of any escape character. These include all non-printable control characters of the first page of the ASCII table (< 32), such as tabulation ([code]\t[/code] in C) and newline ([code]\n[/code] and [code]\r[/code]) characters, but not spaces. + Strips all escape characters from the string. These include all non-printable control characters of the first page of the ASCII table (values from 0 to 31), such as tabulation ([code]\t[/code]) and newline ([code]\n[/code], [code]\r[/code]) characters, but [i]not[/i] spaces. </description> </method> <method name="substr" qualifiers="const"> @@ -766,13 +863,13 @@ <param index="0" name="from" type="int" /> <param index="1" name="len" type="int" default="-1" /> <description> - Returns part of the string from the position [param from] with length [param len]. Argument [param len] is optional and using [code]-1[/code] will return remaining characters from given position. + Returns part of the string from the position [param from] with length [param len]. If [param len] is [code]-1[/code] (as by default), returns the rest of the string starting from the given position. </description> </method> <method name="to_ascii_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is a character array) to ASCII/Latin-1 encoded [PackedByteArray] (which is an array of bytes). The conversion is faster compared to [method to_utf8_buffer], as this method assumes that all the characters in the String are ASCII/Latin-1 characters, unsupported characters are replaced with spaces. + Converts the string to an [url=https://en.wikipedia.org/wiki/ASCII]ASCII[/url]/Latin-1 encoded [PackedByteArray]. This method is slightly faster than [method to_utf8_buffer], but replaces all unsupported characters with spaces. </description> </method> <method name="to_camel_case" qualifiers="const"> @@ -784,23 +881,25 @@ <method name="to_float" qualifiers="const"> <return type="float" /> <description> - Converts a string containing a decimal number into a [code]float[/code]. The method will stop on the first non-number character except the first [code].[/code] (decimal point), and [code]e[/code] which is used for exponential. + Converts the string representing a decimal number into a [float]. This method stops on the first non-number character, except the first decimal point ([code].[/code]) and the exponent letter ([code]e[/code]). See also [method is_valid_float]. [codeblock] - print("12.3".to_float()) # 12.3 - print("1.2.3".to_float()) # 1.2 - print("12ab3".to_float()) # 12 - print("1e3".to_float()) # 1000 + var a = "12.35".to_float() # a is 12.35 + var b = "1.2.3".to_float() # b is 1.2 + var c = "12xy3".to_float() # c is 12.0 + var d = "1e3".to_float() # d is 1000.0 + var e = "Hello!".to_int() # e is 0.0 [/codeblock] </description> </method> <method name="to_int" qualifiers="const"> <return type="int" /> <description> - Converts a string containing an integer number into an [code]int[/code]. The method will remove any non-number character and stop if it encounters a [code].[/code]. + Converts the string representing an integer number into an [int]. This method removes any non-number character and stops at the first decimal point ([code].[/code]). See also [method is_valid_int]. [codeblock] - print("123".to_int()) # 123 - print("a1b2c3".to_int()) # 123 - print("1.2.3".to_int()) # 1 + var a = "123".to_int() # a is 123 + var b = "x1y2z3".to_int() # b is 123 + var c = "-1.2.3".to_int() # c is -1 + var d = "Hello!".to_int() # d is 0 [/codeblock] </description> </method> @@ -831,33 +930,33 @@ <method name="to_utf16_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is an array of characters) to UTF-16 encoded [PackedByteArray] (which is an array of bytes). + Converts the string to a [url=https://en.wikipedia.org/wiki/UTF-16]UTF-16[/url] encoded [PackedByteArray]. </description> </method> <method name="to_utf32_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is an array of characters) to UTF-32 encoded [PackedByteArray] (which is an array of bytes). + Converts the string to a [url=https://en.wikipedia.org/wiki/UTF-32]UTF-32[/url] encoded [PackedByteArray]. </description> </method> <method name="to_utf8_buffer" qualifiers="const"> <return type="PackedByteArray" /> <description> - Converts the String (which is an array of characters) to UTF-8 encode [PackedByteArray] (which is an array of bytes). The conversion is a bit slower than [method to_ascii_buffer], but supports all UTF-8 characters. Therefore, you should prefer this function over [method to_ascii_buffer]. + Converts the string to a [url=https://en.wikipedia.org/wiki/UTF-8]UTF-8[/url] encoded [PackedByteArray]. This method is slightly slower than [method to_ascii_buffer], but supports all UTF-8 characters. For most cases, prefer using this method. </description> </method> <method name="trim_prefix" qualifiers="const"> <return type="String" /> <param index="0" name="prefix" type="String" /> <description> - Removes a given string from the start if it starts with it or leaves the string unchanged. + Removes the given [param prefix] from the start of the string, or returns the string unchanged. </description> </method> <method name="trim_suffix" qualifiers="const"> <return type="String" /> <param index="0" name="suffix" type="String" /> <description> - Removes a given string from the end if it ends with it or leaves the string unchanged. + Removes the given [param suffix] from the end of the string, or returns the string unchanged. </description> </method> <method name="unicode_at" qualifiers="const"> @@ -870,13 +969,15 @@ <method name="uri_decode" qualifiers="const"> <return type="String" /> <description> - Decodes a string in URL encoded format. This is meant to decode parameters in a URL when receiving an HTTP request. + Decodes the string from its URL-encoded format. This method is meant to properly decode the parameters in a URL when receiving an HTTP request. [codeblocks] [gdscript] - print("https://example.org/?escaped=" + "Godot%20Engine%3A%27docs%27".uri_decode()) + var url = "$DOCS_URL/?highlight=Godot%20Engine%3%docs" + print(url.uri_decode()) # Prints "$DOCS_URL/?hightlight=Godot Engine:docs" [/gdscript] [csharp] - GD.Print("https://example.org/?escaped=" + "Godot%20Engine%3a%27Docs%27".URIDecode()); + var url = "$DOCS_URL/?highlight=Godot%20Engine%3%docs" + GD.Print(url.URIDecode()) // Prints "$DOCS_URL/?hightlight=Godot Engine:docs" [/csharp] [/codeblocks] </description> @@ -884,13 +985,19 @@ <method name="uri_encode" qualifiers="const"> <return type="String" /> <description> - Encodes a string to URL friendly format. This is meant to encode parameters in a URL when sending an HTTP request. + Encodes the string to URL-friendly format. This method is meant to properly encode the parameters in a URL when sending an HTTP request. [codeblocks] [gdscript] - print("https://example.org/?escaped=" + "Godot Engine:'docs'".uri_encode()) + var prefix = "$DOCS_URL/?hightlight=" + var url = prefix + "Godot Engine:docs".uri_encode() + + print(url) # Prints "$DOCS_URL/?highlight=Godot%20Engine%3%docs" [/gdscript] [csharp] - GD.Print("https://example.org/?escaped=" + "Godot Engine:'docs'".URIEncode()); + var prefix = "$DOCS_URL/?hightlight="; + var url = prefix + "Godot Engine:docs".URIEncode(); + + GD.Print(url); // Prints "$DOCS_URL/?highlight=Godot%20Engine%3%docs" [/csharp] [/codeblocks] </description> @@ -898,7 +1005,7 @@ <method name="validate_node_name" qualifiers="const"> <return type="String" /> <description> - Removes any characters from the string that are prohibited in [Node] names ([code].[/code] [code]:[/code] [code]@[/code] [code]/[/code] [code]"[/code]). + Removes all characters that are not allowed in [member Node.name] from the string ([code].[/code] [code]:[/code] [code]@[/code] [code]/[/code] [code]"[/code] [code]%[/code]). </description> </method> <method name="xml_escape" qualifiers="const"> diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index b3e55b5cd0..4fc6ee3312 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -235,7 +235,7 @@ <param index="1" name="size" type="Vector2i" /> <param index="2" name="glyph" type="int" /> <description> - Returns resource id of the cache texture containing the glyph. + Returns resource ID of the cache texture containing the glyph. [b]Note:[/b] If there are pending glyphs to render, calling this function might trigger the texture cache update. </description> </method> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 01246f0c2e..e9e2a738d3 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -174,7 +174,7 @@ Creates a new [TileMapPattern] from the given layer and set of cells. </description> </method> - <method name="get_surrounding_tiles"> + <method name="get_surrounding_cells"> <return type="Vector2i[]" /> <param index="0" name="coords" type="Vector2i" /> <description> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 6a016c3ebd..629c271417 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -70,7 +70,7 @@ <return type="int" /> <param index="0" name="position" type="Vector2" /> <description> - Returns the button id at [param position], or -1 if no button is there. + Returns the button ID at [param position], or -1 if no button is there. </description> </method> <method name="get_column_at_position" qualifiers="const"> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index a8ffef427f..ec6b166e57 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -78,7 +78,7 @@ <param index="0" name="column" type="int" /> <param index="1" name="id" type="int" /> <description> - Returns the button index if there is a button with id [param id] in column [param column], otherwise returns -1. + Returns the button index if there is a button with ID [param id] in column [param column], otherwise returns -1. </description> </method> <method name="get_button_count" qualifiers="const"> @@ -93,7 +93,7 @@ <param index="0" name="column" type="int" /> <param index="1" name="button_idx" type="int" /> <description> - Returns the id for the button at index [param button_idx] in column [param column]. + Returns the ID for the button at index [param button_idx] in column [param column]. </description> </method> <method name="get_button_tooltip_text" qualifiers="const"> diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index 8707ef003a..16ca486e4a 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -10,6 +10,66 @@ <tutorials> </tutorials> <methods> + <method name="add_theme_color_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <param index="1" name="color" type="Color" /> + <description> + Creates a local override for a theme [Color] with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_color_override]. + See also [method get_theme_color] and [method Control.add_theme_color_override] for more details. + </description> + </method> + <method name="add_theme_constant_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <param index="1" name="constant" type="int" /> + <description> + Creates a local override for a theme constant with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_constant_override]. + See also [method get_theme_constant]. + </description> + </method> + <method name="add_theme_font_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <param index="1" name="font" type="Font" /> + <description> + Creates a local override for a theme [Font] with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_font_override]. + See also [method get_theme_font]. + </description> + </method> + <method name="add_theme_font_size_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <param index="1" name="font_size" type="int" /> + <description> + Creates a local override for a theme font size with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_font_size_override]. + See also [method get_theme_font_size]. + </description> + </method> + <method name="add_theme_icon_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <param index="1" name="texture" type="Texture2D" /> + <description> + Creates a local override for a theme icon with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_icon_override]. + See also [method get_theme_icon]. + </description> + </method> + <method name="add_theme_stylebox_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <param index="1" name="stylebox" type="StyleBox" /> + <description> + Creates a local override for a theme [StyleBox] with the specified [param name]. Local overrides always take precedence when fetching theme items for the control. An override can be removed with [method remove_theme_stylebox_override]. + See also [method get_theme_stylebox] and [method Control.add_theme_stylebox_override] for more details. + </description> + </method> + <method name="begin_bulk_theme_override"> + <return type="void" /> + <description> + Prevents [code]*_theme_*_override[/code] methods from emitting [constant NOTIFICATION_THEME_CHANGED] until [method end_bulk_theme_override] is called. + </description> + </method> <method name="can_draw" qualifiers="const"> <return type="bool" /> <description> @@ -22,6 +82,12 @@ Requests an update of the [Window] size to fit underlying [Control] nodes. </description> </method> + <method name="end_bulk_theme_override"> + <return type="void" /> + <description> + Ends a bulk theme override update. See [method begin_bulk_theme_override]. + </description> + </method> <method name="get_contents_minimum_size" qualifiers="const"> <return type="Vector2" /> <description> @@ -41,7 +107,13 @@ Returns layout direction and text writing direction. </description> </method> - <method name="get_real_size" qualifiers="const"> + <method name="get_position_with_decorations" qualifiers="const"> + <return type="Vector2i" /> + <description> + Returns the window's position including its border. + </description> + </method> + <method name="get_size_with_decorations" qualifiers="const"> <return type="Vector2i" /> <description> Returns the window's size including its border. @@ -52,7 +124,7 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns the [Color] at [param name] if the theme has [param theme_type]. + Returns a [Color] from the first matching [Theme] in the tree if that [Theme] has a color item with the specified [param name] and [param theme_type]. See [method Control.get_theme_color] for more details. </description> </method> @@ -61,29 +133,29 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns the constant at [param name] if the theme has [param theme_type]. + Returns a constant from the first matching [Theme] in the tree if that [Theme] has a constant item with the specified [param name] and [param theme_type]. See [method Control.get_theme_color] for more details. </description> </method> <method name="get_theme_default_base_scale" qualifiers="const"> <return type="float" /> <description> - Returns the default base scale defined in the attached [Theme]. - See [member Theme.default_base_scale] for more details. + Returns the default base scale value from the first matching [Theme] in the tree if that [Theme] has a valid [member Theme.default_base_scale] value. + See [method Control.get_theme_color] for details. </description> </method> <method name="get_theme_default_font" qualifiers="const"> <return type="Font" /> <description> - Returns the default [Font] defined in the attached [Theme]. - See [member Theme.default_font] for more details. + Returns the default font from the first matching [Theme] in the tree if that [Theme] has a valid [member Theme.default_font] value. + See [method Control.get_theme_color] for details. </description> </method> <method name="get_theme_default_font_size" qualifiers="const"> <return type="int" /> <description> - Returns the default font size defined in the attached [Theme]. - See [member Theme.default_font_size] for more details. + Returns the default font size value from the first matching [Theme] in the tree if that [Theme] has a valid [member Theme.default_font_size] value. + See [method Control.get_theme_color] for details. </description> </method> <method name="get_theme_font" qualifiers="const"> @@ -91,8 +163,8 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns the [Font] at [param name] if the theme has [param theme_type]. - See [method Control.get_theme_color] for more details. + Returns a [Font] from the first matching [Theme] in the tree if that [Theme] has a font item with the specified [param name] and [param theme_type]. + See [method Control.get_theme_color] for details. </description> </method> <method name="get_theme_font_size" qualifiers="const"> @@ -100,8 +172,8 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns the font size at [param name] if the theme has [param theme_type]. - See [method Control.get_theme_color] for more details. + Returns a font size from the first matching [Theme] in the tree if that [Theme] has a font size item with the specified [param name] and [param theme_type]. + See [method Control.get_theme_color] for details. </description> </method> <method name="get_theme_icon" qualifiers="const"> @@ -109,8 +181,8 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns the icon at [param name] if the theme has [param theme_type]. - See [method Control.get_theme_color] for more details. + Returns an icon from the first matching [Theme] in the tree if that [Theme] has an icon item with the specified [param name] and [param theme_type]. + See [method Control.get_theme_color] for details. </description> </method> <method name="get_theme_stylebox" qualifiers="const"> @@ -118,8 +190,8 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns the [StyleBox] at [param name] if the theme has [param theme_type]. - See [method Control.get_theme_color] for more details. + Returns a [StyleBox] from the first matching [Theme] in the tree if that [Theme] has a stylebox item with the specified [param name] and [param theme_type]. + See [method Control.get_theme_color] for details. </description> </method> <method name="grab_focus"> @@ -139,7 +211,16 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns [code]true[/code] if [Color] with [param name] is in [param theme_type]. + Returns [code]true[/code] if there is a matching [Theme] in the tree that has a color item with the specified [param name] and [param theme_type]. + See [method Control.get_theme_color] for details. + </description> + </method> + <method name="has_theme_color_override" qualifiers="const"> + <return type="bool" /> + <param index="0" name="name" type="StringName" /> + <description> + Returns [code]true[/code] if there is a local override for a theme [Color] with the specified [param name] in this [Control] node. + See [method add_theme_color_override]. </description> </method> <method name="has_theme_constant" qualifiers="const"> @@ -147,7 +228,16 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns [code]true[/code] if constant with [param name] is in [param theme_type]. + Returns [code]true[/code] if there is a matching [Theme] in the tree that has a constant item with the specified [param name] and [param theme_type]. + See [method Control.get_theme_color] for details. + </description> + </method> + <method name="has_theme_constant_override" qualifiers="const"> + <return type="bool" /> + <param index="0" name="name" type="StringName" /> + <description> + Returns [code]true[/code] if there is a local override for a theme constant with the specified [param name] in this [Control] node. + See [method add_theme_constant_override]. </description> </method> <method name="has_theme_font" qualifiers="const"> @@ -155,7 +245,16 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns [code]true[/code] if [Font] with [param name] is in [param theme_type]. + Returns [code]true[/code] if there is a matching [Theme] in the tree that has a font item with the specified [param name] and [param theme_type]. + See [method Control.get_theme_color] for details. + </description> + </method> + <method name="has_theme_font_override" qualifiers="const"> + <return type="bool" /> + <param index="0" name="name" type="StringName" /> + <description> + Returns [code]true[/code] if there is a local override for a theme [Font] with the specified [param name] in this [Control] node. + See [method add_theme_font_override]. </description> </method> <method name="has_theme_font_size" qualifiers="const"> @@ -163,7 +262,16 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns [code]true[/code] if font size with [param name] is in [param theme_type]. + Returns [code]true[/code] if there is a matching [Theme] in the tree that has a font size item with the specified [param name] and [param theme_type]. + See [method Control.get_theme_color] for details. + </description> + </method> + <method name="has_theme_font_size_override" qualifiers="const"> + <return type="bool" /> + <param index="0" name="name" type="StringName" /> + <description> + Returns [code]true[/code] if there is a local override for a theme font size with the specified [param name] in this [Control] node. + See [method add_theme_font_size_override]. </description> </method> <method name="has_theme_icon" qualifiers="const"> @@ -171,7 +279,16 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns [code]true[/code] if icon with [param name] is in [param theme_type]. + Returns [code]true[/code] if there is a matching [Theme] in the tree that has an icon item with the specified [param name] and [param theme_type]. + See [method Control.get_theme_color] for details. + </description> + </method> + <method name="has_theme_icon_override" qualifiers="const"> + <return type="bool" /> + <param index="0" name="name" type="StringName" /> + <description> + Returns [code]true[/code] if there is a local override for a theme icon with the specified [param name] in this [Control] node. + See [method add_theme_icon_override]. </description> </method> <method name="has_theme_stylebox" qualifiers="const"> @@ -179,7 +296,16 @@ <param index="0" name="name" type="StringName" /> <param index="1" name="theme_type" type="StringName" default="""" /> <description> - Returns [code]true[/code] if [StyleBox] with [param name] is in [param theme_type]. + Returns [code]true[/code] if there is a matching [Theme] in the tree that has a stylebox item with the specified [param name] and [param theme_type]. + See [method Control.get_theme_color] for details. + </description> + </method> + <method name="has_theme_stylebox_override" qualifiers="const"> + <return type="bool" /> + <param index="0" name="name" type="StringName" /> + <description> + Returns [code]true[/code] if there is a local override for a theme [StyleBox] with the specified [param name] in this [Control] node. + See [method add_theme_stylebox_override]. </description> </method> <method name="hide"> @@ -258,6 +384,48 @@ If the [Window] is embedded, has the same effect as [method popup]. </description> </method> + <method name="remove_theme_color_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <description> + Removes a local override for a theme [Color] with the specified [param name] previously added by [method add_theme_color_override] or via the Inspector dock. + </description> + </method> + <method name="remove_theme_constant_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <description> + Removes a local override for a theme constant with the specified [param name] previously added by [method add_theme_constant_override] or via the Inspector dock. + </description> + </method> + <method name="remove_theme_font_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <description> + Removes a local override for a theme [Font] with the specified [param name] previously added by [method add_theme_font_override] or via the Inspector dock. + </description> + </method> + <method name="remove_theme_font_size_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <description> + Removes a local override for a theme font size with the specified [param name] previously added by [method add_theme_font_size_override] or via the Inspector dock. + </description> + </method> + <method name="remove_theme_icon_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <description> + Removes a local override for a theme icon with the specified [param name] previously added by [method add_theme_icon_override] or via the Inspector dock. + </description> + </method> + <method name="remove_theme_stylebox_override"> + <return type="void" /> + <param index="0" name="name" type="StringName" /> + <description> + Removes a local override for a theme [StyleBox] with the specified [param name] previously added by [method add_theme_stylebox_override] or via the Inspector dock. + </description> + </method> <method name="request_attention"> <return type="void" /> <description> @@ -367,8 +535,8 @@ The window's size in pixels. </member> <member name="theme" type="Theme" setter="set_theme" getter="get_theme"> - The [Theme] resource that determines the style of the underlying [Control] nodes. - [Window] styles will have no effect unless the window is embedded. + The [Theme] resource this node and all its [Control] and [Window] children use. If a child node has its own [Theme] resource set, theme items are merged with child's definitions having higher priority. + [b]Note:[/b] [Window] styles will have no effect unless the window is embedded. </member> <member name="theme_type_variation" type="StringName" setter="set_theme_type_variation" getter="get_theme_type_variation" default="&"""> The name of a theme type variation used by this [Window] to look up its own theme items. See [member Control.theme_type_variation] for more details. diff --git a/doc/classes/XRInterfaceExtension.xml b/doc/classes/XRInterfaceExtension.xml index 0fe54e947f..5ad67a7ea9 100644 --- a/doc/classes/XRInterfaceExtension.xml +++ b/doc/classes/XRInterfaceExtension.xml @@ -24,7 +24,7 @@ <method name="_get_camera_feed_id" qualifiers="virtual const"> <return type="int" /> <description> - Returns the camera feed id for the [CameraFeed] registered with the [CameraServer] that should be presented as the background on an AR capable device (if applicable). + Returns the camera feed ID for the [CameraFeed] registered with the [CameraServer] that should be presented as the background on an AR capable device (if applicable). </description> </method> <method name="_get_camera_transform" qualifiers="virtual"> diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index 8960c66acc..1f71b77cbd 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -1565,7 +1565,7 @@ def format_text_block( escape_post = False # Tag is a reference to a class. - if tag_text in state.classes: + if tag_text in state.classes and not inside_code: if tag_text == state.current_class: # Don't create a link to the same class, format it as strong emphasis. tag_text = f"**{tag_text}**" diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index e5d4077393..c7e7227916 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -454,7 +454,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ update_skeletons = false; } // Canvas group begins here, render until before this item - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, false); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, r_sdf_used); item_count = 0; if (ci->canvas_group_owner->canvas_group->mode != RS::CANVAS_GROUP_MODE_TRANSPARENT) { @@ -485,7 +485,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ mesh_storage->update_mesh_instances(); update_skeletons = false; } - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, true); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, r_sdf_used, true); item_count = 0; if (ci->canvas_group->blur_mipmaps) { @@ -504,7 +504,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ } //render anything pending, including clearing if no items - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, false); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, r_sdf_used); item_count = 0; texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps); @@ -530,7 +530,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ mesh_storage->update_mesh_instances(); update_skeletons = false; } - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, false); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, r_sdf_used); //then reset item_count = 0; } @@ -549,7 +549,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state.current_buffer = (state.current_buffer + 1) % state.canvas_instance_data_buffers.size(); } -void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, uint32_t &r_last_index, bool p_to_backbuffer) { +void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, uint32_t &r_last_index, bool &r_sdf_used, bool p_to_backbuffer) { GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton(); canvas_begin(p_to_render_target, p_to_backbuffer); @@ -617,7 +617,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou GLES3::CanvasShaderData::BlendMode blend_mode = shader_data_cache ? shader_data_cache->blend_mode : GLES3::CanvasShaderData::BLEND_MODE_MIX; - _record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken); + _record_item_commands(ci, p_to_render_target, p_canvas_transform_inverse, current_clip, blend_mode, p_lights, index, batch_broken, r_sdf_used); } if (index == 0) { @@ -749,7 +749,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou r_last_index += index; } -void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken) { +void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used) { RenderingServer::CanvasItemTextureFilter texture_filter = p_item->texture_filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT ? state.default_filter : p_item->texture_filter; if (texture_filter != state.canvas_instance_batches[state.current_batch_index].filter) { @@ -1145,6 +1145,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend } else { particles_storage->particles_set_canvas_sdf_collision(pt->particles, false, Transform2D(), Rect2(), 0); } + r_sdf_used |= particles_storage->particles_has_collision(particles); } state.canvas_instance_batches[state.current_batch_index].command = c; diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h index 0a03d43d07..bd87973404 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.h +++ b/drivers/gles3/rasterizer_canvas_gles3.h @@ -351,8 +351,8 @@ public: void _prepare_canvas_texture(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, uint32_t &r_index, Size2 &r_texpixel_size); void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used) override; - void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, uint32_t &r_last_index, bool p_to_backbuffer = false); - void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch); + void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, uint32_t &r_last_index, bool &r_sdf_used, bool p_to_backbuffer = false); + void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used); void _render_batch(Light *p_lights, uint32_t p_index); bool _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization); void _new_batch(bool &r_batch_broken, uint32_t &r_index); diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index 1b42b55425..7b4131b3a3 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -285,6 +285,15 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display ERR_FAIL_COND(!rt); + // We normally render to the render target upside down, so flip Y when blitting to the screen. + bool flip_y = true; + if (rt->overridden.color.is_valid()) { + // If we've overridden the render target's color texture, that means we + // didn't render upside down, so we don't need to flip it. + // We're probably rendering directly to an XR device. + flip_y = false; + } + GLuint read_fbo = 0; if (rt->view_count > 1) { glGenFramebuffers(1, &read_fbo); @@ -296,10 +305,9 @@ void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, Display glReadBuffer(GL_COLOR_ATTACHMENT0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, GLES3::TextureStorage::system_fbo); - // Flip content upside down to correct for coordinates. Vector2i screen_rect_end = p_screen_rect.get_end(); glBlitFramebuffer(0, 0, rt->size.x, rt->size.y, - p_screen_rect.position.x, screen_rect_end.y, screen_rect_end.x, p_screen_rect.position.y, + p_screen_rect.position.x, flip_y ? screen_rect_end.y : p_screen_rect.position.y, screen_rect_end.x, flip_y ? p_screen_rect.position.y : screen_rect_end.y, GL_COLOR_BUFFER_BIT, GL_NEAREST); if (read_fbo != 0) { diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 2743801466..247b89658a 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -788,7 +788,7 @@ void RasterizerSceneGLES3::_draw_sky(RID p_env, const Projection &p_projection, } Basis sky_transform = environment_get_sky_orientation(p_env); sky_transform.invert(); - sky_transform = p_transform.basis * sky_transform; + sky_transform = sky_transform * p_transform.basis; bool success = material_storage->shaders.sky_shader.version_bind_shader(shader_data->version, SkyShaderGLES3::MODE_BACKGROUND); if (!success) { @@ -1902,7 +1902,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glColorMask(0, 0, 0, 0); glClearDepth(1.0f); glClear(GL_DEPTH_BUFFER_BIT); - uint32_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | + uint64_t spec_constant = SceneShaderGLES3::DISABLE_FOG | SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL | SceneShaderGLES3::DISABLE_LIGHTMAP | SceneShaderGLES3::DISABLE_LIGHT_OMNI | SceneShaderGLES3::DISABLE_LIGHT_SPOT; @@ -1943,7 +1943,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_ glClearBufferfv(GL_COLOR, 0, clear_color.components); } RENDER_TIMESTAMP("Render Opaque Pass"); - uint32_t spec_constant_base_flags = 0; + uint64_t spec_constant_base_flags = 0; { // Specialization Constants that apply for entire rendering pass. @@ -2014,8 +2014,10 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, GeometryInstanceGLES3 *prev_inst = nullptr; SceneShaderGLES3::ShaderVariant prev_variant = SceneShaderGLES3::ShaderVariant::MODE_COLOR; SceneShaderGLES3::ShaderVariant shader_variant = SceneShaderGLES3::MODE_COLOR; // Assigned to silence wrong -Wmaybe-initialized + uint64_t prev_spec_constants = 0; - uint32_t base_spec_constants = p_params->spec_constant_base_flags; + // Specializations constants used by all instances in the scene. + uint64_t base_spec_constants = p_params->spec_constant_base_flags; if (p_render_data->view_count > 1) { base_spec_constants |= SceneShaderGLES3::USE_MULTIVIEW; @@ -2235,8 +2237,18 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, instance_variant = SceneShaderGLES3::ShaderVariant(1 + int(shader_variant)); } - if (prev_shader != shader || prev_variant != instance_variant) { - bool success = material_storage->shaders.scene_shader.version_bind_shader(shader->version, instance_variant, base_spec_constants); + uint64_t spec_constants = base_spec_constants; + + if (inst->omni_light_count == 0) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_OMNI; + } + + if (inst->spot_light_count == 0) { + spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT; + } + + if (prev_shader != shader || prev_variant != instance_variant || spec_constants != prev_spec_constants) { + bool success = material_storage->shaders.scene_shader.version_bind_shader(shader->version, instance_variant, spec_constants); if (!success) { continue; } @@ -2248,29 +2260,30 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, opaque_prepass_threshold = 0.1; } - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OPAQUE_PREPASS_THRESHOLD, opaque_prepass_threshold, shader->version, instance_variant, base_spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OPAQUE_PREPASS_THRESHOLD, opaque_prepass_threshold, shader->version, instance_variant, spec_constants); prev_shader = shader; prev_variant = instance_variant; + prev_spec_constants = spec_constants; } if (prev_inst != inst || prev_shader != shader || prev_variant != instance_variant) { // Rebind the light indices. - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OMNI_LIGHT_COUNT, inst->omni_light_count, shader->version, instance_variant, base_spec_constants); - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::SPOT_LIGHT_COUNT, inst->spot_light_count, shader->version, instance_variant, base_spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::OMNI_LIGHT_COUNT, inst->omni_light_count, shader->version, instance_variant, spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::SPOT_LIGHT_COUNT, inst->spot_light_count, shader->version, instance_variant, spec_constants); if (inst->omni_light_count) { - glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::OMNI_LIGHT_INDICES, shader->version, instance_variant, base_spec_constants), inst->omni_light_count, inst->omni_light_gl_cache.ptr()); + glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::OMNI_LIGHT_INDICES, shader->version, instance_variant, spec_constants), inst->omni_light_count, inst->omni_light_gl_cache.ptr()); } if (inst->spot_light_count) { - glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::SPOT_LIGHT_INDICES, shader->version, instance_variant, base_spec_constants), inst->spot_light_count, inst->spot_light_gl_cache.ptr()); + glUniform1uiv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::SPOT_LIGHT_INDICES, shader->version, instance_variant, spec_constants), inst->spot_light_count, inst->spot_light_gl_cache.ptr()); } prev_inst = inst; } - material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::WORLD_TRANSFORM, world_transform, shader->version, instance_variant, base_spec_constants); + material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::WORLD_TRANSFORM, world_transform, shader->version, instance_variant, spec_constants); if (inst->instance_count > 0) { // Using MultiMesh or Particles. // Bind instance buffers. diff --git a/drivers/gles3/rasterizer_scene_gles3.h b/drivers/gles3/rasterizer_scene_gles3.h index cd1f44e679..255e62fc33 100644 --- a/drivers/gles3/rasterizer_scene_gles3.h +++ b/drivers/gles3/rasterizer_scene_gles3.h @@ -392,10 +392,10 @@ private: GeometryInstanceSurface **elements = nullptr; int element_count = 0; bool reverse_cull = false; - uint32_t spec_constant_base_flags = 0; + uint64_t spec_constant_base_flags = 0; bool force_wireframe = false; - RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint32_t p_spec_constant_base_flags, bool p_force_wireframe = false) { + RenderListParameters(GeometryInstanceSurface **p_elements, int p_element_count, bool p_reverse_cull, uint64_t p_spec_constant_base_flags, bool p_force_wireframe = false) { elements = p_elements; element_count = p_element_count; reverse_cull = p_reverse_cull; diff --git a/drivers/gles3/shader_gles3.cpp b/drivers/gles3/shader_gles3.cpp index 1dcd17ea0e..69c3ff7c8e 100644 --- a/drivers/gles3/shader_gles3.cpp +++ b/drivers/gles3/shader_gles3.cpp @@ -36,6 +36,11 @@ #include "core/io/dir_access.h" #include "core/io/file_access.h" +static String _mkid(const String &p_id) { + String id = "m_" + p_id.replace("__", "_dus_"); + return id.replace("__", "_dus_"); //doubleunderscore is reserved in glsl +} + void ShaderGLES3::_add_stage(const char *p_code, StageType p_stage_type) { Vector<String> lines = String(p_code).split("\n"); @@ -425,7 +430,7 @@ void ShaderGLES3::_compile_specialization(Version::Specialization &spec, uint32_ } // textures for (int i = 0; i < p_version->texture_uniforms.size(); i++) { - String native_uniform_name = p_version->texture_uniforms[i]; + String native_uniform_name = _mkid(p_version->texture_uniforms[i]); GLint location = glGetUniformLocation(spec.id, (native_uniform_name).ascii().get_data()); glUniform1i(location, i + base_texture_index); } diff --git a/drivers/gles3/shaders/canvas.glsl b/drivers/gles3/shaders/canvas.glsl index a61ea1587d..c1c26ed963 100644 --- a/drivers/gles3/shaders/canvas.glsl +++ b/drivers/gles3/shaders/canvas.glsl @@ -41,8 +41,6 @@ layout(std140) uniform MaterialUniforms{ //ubo:4 #include "canvas_uniforms_inc.glsl" #include "stdlib_inc.glsl" -uniform sampler2D transforms_texture; //texunit:-1 - out vec2 uv_interp; out vec4 color_interp; out vec2 vertex_interp; diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index 32557d3314..fa68f0063f 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -779,7 +779,7 @@ float get_omni_attenuation(float distance, float inv_range, float decay) { nd *= nd; // nd^2 return nd * pow(max(distance, 0.0001), -decay); } - +#ifndef DISABLE_LIGHT_OMNI void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, @@ -821,7 +821,9 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f diffuse_light, specular_light); } +#endif // !DISABLE_LIGHT_OMNI +#ifndef DISABLE_LIGHT_SPOT void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f0, float roughness, float metallic, float shadow, vec3 albedo, inout float alpha, #ifdef LIGHT_BACKLIGHT_USED vec3 backlight, @@ -869,6 +871,7 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 f #endif diffuse_light, specular_light); } +#endif // !DISABLE_LIGHT_SPOT #endif // !defined(DISABLE_LIGHT_DIRECTIONAL) || !defined(DISABLE_LIGHT_OMNI) && !defined(DISABLE_LIGHT_SPOT) #ifndef MODE_RENDER_DEPTH diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index 285f32f1a5..5bbbc7b91b 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -334,7 +334,14 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) for (int i = 0; i < p_surface.bone_aabbs.size(); i++) { const AABB &bone = p_surface.bone_aabbs[i]; if (bone.has_volume()) { - mesh->bone_aabbs.write[i].merge_with(bone); + AABB &mesh_bone = mesh->bone_aabbs.write[i]; + if (mesh_bone != AABB()) { + // Already initialized, merge AABBs. + mesh_bone.merge_with(bone); + } else { + // Not yet initialized, copy the bone AABB. + mesh_bone = bone; + } } } mesh->aabb.merge_with(p_surface.aabb); diff --git a/drivers/gles3/storage/utilities.cpp b/drivers/gles3/storage/utilities.cpp index fe900c7cfb..02a110e3e5 100644 --- a/drivers/gles3/storage/utilities.cpp +++ b/drivers/gles3/storage/utilities.cpp @@ -156,6 +156,9 @@ bool Utilities::free(RID p_rid) { } else if (GLES3::ParticlesStorage::get_singleton()->owns_particles_collision_instance(p_rid)) { GLES3::ParticlesStorage::get_singleton()->particles_collision_instance_free(p_rid); return true; + } else if (GLES3::MeshStorage::get_singleton()->owns_skeleton(p_rid)) { + GLES3::MeshStorage::get_singleton()->skeleton_free(p_rid); + return true; } else { return false; } diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 028c7dca6f..ee251e7acf 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -378,9 +378,7 @@ Error VulkanContext::_obtain_vulkan_version() { uint32_t api_version; VkResult res = func(&api_version); if (res == VK_SUCCESS) { - vulkan_major = VK_API_VERSION_MAJOR(api_version); - vulkan_minor = VK_API_VERSION_MINOR(api_version); - vulkan_patch = VK_API_VERSION_PATCH(api_version); + instance_api_version = api_version; } else { // According to the documentation this shouldn't fail with anything except a memory allocation error // in which case we're in deep trouble anyway. @@ -388,13 +386,7 @@ Error VulkanContext::_obtain_vulkan_version() { } } else { print_line("vkEnumerateInstanceVersion not available, assuming Vulkan 1.0."); - } - - // We don't go above 1.2. - if ((vulkan_major > 1) || (vulkan_major == 1 && vulkan_minor > 2)) { - vulkan_major = 1; - vulkan_minor = 2; - vulkan_patch = 0; + instance_api_version = VK_API_VERSION_1_0; } return OK; @@ -832,7 +824,7 @@ Error VulkanContext::_check_capabilities() { VkPhysicalDeviceProperties2 physicalDeviceProperties{}; void *nextptr = nullptr; - if (!(vulkan_major == 1 && vulkan_minor == 0)) { + if (device_api_version >= VK_API_VERSION_1_1) { // Vulkan 1.1 or higher subgroupProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES; subgroupProperties.pNext = nextptr; @@ -937,15 +929,21 @@ Error VulkanContext::_create_instance() { enabled_extension_names[enabled_extension_count++] = extension_name.ptr(); } + // We'll set application version to the Vulkan version we're developing against, even if our instance is based on + // an older Vulkan version, devices can still support newer versions of Vulkan. + // The exception is when we're on Vulkan 1.0, we should not set this to anything but 1.0. + // Note that this value is only used by validation layers to warn us about version issues. + uint32_t application_api_version = instance_api_version == VK_API_VERSION_1_0 ? VK_API_VERSION_1_0 : VK_API_VERSION_1_2; + CharString cs = GLOBAL_GET("application/config/name").operator String().utf8(); const VkApplicationInfo app = { /*sType*/ VK_STRUCTURE_TYPE_APPLICATION_INFO, /*pNext*/ nullptr, /*pApplicationName*/ cs.get_data(), - /*applicationVersion*/ 0, + /*applicationVersion*/ 0, // It would be really nice if we store a version number in project settings, say "application/config/version" /*pEngineName*/ VERSION_NAME, /*engineVersion*/ VK_MAKE_VERSION(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH), - /*apiVersion*/ VK_MAKE_VERSION(vulkan_major, vulkan_minor, 0) + /*apiVersion*/ application_api_version }; VkInstanceCreateInfo inst_info{}; inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; @@ -1257,12 +1255,12 @@ Error VulkanContext::_create_physical_device(VkSurfaceKHR p_surface) { } } - print_line( - "Vulkan API " + itos(vulkan_major) + "." + itos(vulkan_minor) + "." + itos(vulkan_patch) + - " - " + "Using Vulkan Device #" + itos(device_index) + ": " + device_vendor + " - " + device_name); - + // Get device version device_api_version = gpu_props.apiVersion; + // Output our device version + print_line("Vulkan API " + get_device_api_version() + " - " + "Using Vulkan Device #" + itos(device_index) + ": " + device_vendor + " - " + device_name); + { Error _err = _initialize_device_extensions(); if (_err != OK) { @@ -1345,7 +1343,7 @@ Error VulkanContext::_create_device() { VkPhysicalDeviceVulkan11Features vulkan11features = {}; VkPhysicalDevice16BitStorageFeaturesKHR storage_feature = {}; VkPhysicalDeviceMultiviewFeatures multiview_features = {}; - if (vulkan_major > 1 || vulkan_minor >= 2) { + if (device_api_version >= VK_API_VERSION_1_2) { // In Vulkan 1.2 and newer we use a newer struct to enable various features. vulkan11features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES; @@ -1373,7 +1371,7 @@ Error VulkanContext::_create_device() { storage_feature.storageInputOutput16 = storage_buffer_capabilities.storage_input_output_16; nextptr = &storage_feature; - if (vulkan_major == 1 && vulkan_minor == 1) { + if (device_api_version >= VK_API_VERSION_1_1) { // any Vulkan 1.1.x version multiview_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES; multiview_features.pNext = nextptr; multiview_features.multiview = multiview_capabilities.is_supported; @@ -2644,7 +2642,7 @@ RenderingDevice::DeviceType VulkanContext::get_device_type() const { } String VulkanContext::get_device_api_version() const { - return vformat("%d.%d.%d", vulkan_major, vulkan_minor, vulkan_patch); + return vformat("%d.%d.%d", VK_API_VERSION_MAJOR(device_api_version), VK_API_VERSION_MINOR(device_api_version), VK_API_VERSION_PATCH(device_api_version)); } String VulkanContext::get_device_pipeline_cache_uuid() const { diff --git a/drivers/vulkan/vulkan_context.h b/drivers/vulkan/vulkan_context.h index 0d49f5fe9f..f37bee72eb 100644 --- a/drivers/vulkan/vulkan_context.h +++ b/drivers/vulkan/vulkan_context.h @@ -110,10 +110,7 @@ private: bool device_initialized = false; bool inst_initialized = false; - // Vulkan 1.0 doesn't return version info so we assume this by default until we know otherwise. - uint32_t vulkan_major = 1; - uint32_t vulkan_minor = 0; - uint32_t vulkan_patch = 0; + uint32_t instance_api_version = VK_API_VERSION_1_0; SubgroupCapabilities subgroup_capabilities; MultiviewCapabilities multiview_capabilities; VRSCapabilities vrs_capabilities; @@ -276,8 +273,8 @@ public: bool supports_renderpass2() const { return is_device_extension_enabled(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); } VkResult vkCreateRenderPass2KHR(VkDevice p_device, const VkRenderPassCreateInfo2 *p_create_info, const VkAllocationCallbacks *p_allocator, VkRenderPass *p_render_pass); - uint32_t get_vulkan_major() const { return vulkan_major; }; - uint32_t get_vulkan_minor() const { return vulkan_minor; }; + uint32_t get_vulkan_major() const { return VK_API_VERSION_MAJOR(device_api_version); }; + uint32_t get_vulkan_minor() const { return VK_API_VERSION_MINOR(device_api_version); }; const SubgroupCapabilities &get_subgroup_capabilities() const { return subgroup_capabilities; }; const MultiviewCapabilities &get_multiview_capabilities() const { return multiview_capabilities; }; const VRSCapabilities &get_vrs_capabilities() const { return vrs_capabilities; }; diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 926c01b334..7ae0db7b48 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -873,6 +873,10 @@ void CodeTextEditor::_reset_zoom() { } void CodeTextEditor::_line_col_changed() { + if (!code_complete_timer->is_stopped() && code_complete_timer_line != text_editor->get_caret_line()) { + code_complete_timer->stop(); + } + String line = text_editor->get_line(text_editor->get_caret_line()); int positional_column = 0; @@ -902,6 +906,7 @@ void CodeTextEditor::_line_col_changed() { void CodeTextEditor::_text_changed() { if (text_editor->is_insert_text_operation()) { + code_complete_timer_line = text_editor->get_caret_line(); code_complete_timer->start(); } diff --git a/editor/code_editor.h b/editor/code_editor.h index ded7518287..4d7034fbd0 100644 --- a/editor/code_editor.h +++ b/editor/code_editor.h @@ -158,6 +158,7 @@ class CodeTextEditor : public VBoxContainer { Label *info = nullptr; Timer *idle = nullptr; Timer *code_complete_timer = nullptr; + int code_complete_timer_line = 0; Timer *font_resize_timer = nullptr; int font_resize_val; diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 2bd77bf99c..1f0cc1dc77 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -788,23 +788,7 @@ bool ConnectionsDock::_is_item_signal(TreeItem &p_item) { } bool ConnectionsDock::_is_connection_inherited(Connection &p_connection) { - Node *scene_root = EditorNode::get_singleton()->get_edited_scene(); - Ref<PackedScene> scn = ResourceLoader::load(scene_root->get_scene_file_path()); - ERR_FAIL_NULL_V(scn, false); - - Ref<SceneState> state = scn->get_state(); - ERR_FAIL_NULL_V(state, false); - - Node *source = Object::cast_to<Node>(p_connection.signal.get_object()); - Node *target = Object::cast_to<Node>(p_connection.callable.get_object()); - - const NodePath source_path = scene_root->get_path_to(source); - const NodePath target_path = scene_root->get_path_to(target); - const StringName signal_name = p_connection.signal.get_name(); - const StringName method_name = p_connection.callable.get_method(); - - // If it cannot be found in PackedScene, this connection was inherited. - return !state->has_connection(source_path, signal_name, target_path, method_name, true); + return bool(p_connection.flags & CONNECT_INHERITED); } /* diff --git a/editor/editor_command_palette.cpp b/editor/editor_command_palette.cpp index b92b0fca59..609afeb40a 100644 --- a/editor/editor_command_palette.cpp +++ b/editor/editor_command_palette.cpp @@ -38,6 +38,9 @@ EditorCommandPalette *EditorCommandPalette::singleton = nullptr; +static Rect2i prev_rect = Rect2i(); +static bool was_showed = false; + float EditorCommandPalette::_score_path(const String &p_search, const String &p_path) { float score = 0.9f + .1f * (p_search.length() / (float)p_path.length()); @@ -145,6 +148,17 @@ void EditorCommandPalette::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_command", "key_name"), &EditorCommandPalette::remove_command); } +void EditorCommandPalette::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + prev_rect = Rect2i(get_position(), get_size()); + was_showed = true; + } + } break; + } +} + void EditorCommandPalette::_sbox_input(const Ref<InputEvent> &p_ie) { Ref<InputEventKey> k = p_ie; if (k.is_valid()) { @@ -171,12 +185,10 @@ void EditorCommandPalette::_confirmed() { } void EditorCommandPalette::open_popup() { - static bool was_showed = false; - if (!was_showed) { - was_showed = true; - popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); + if (was_showed) { + popup(prev_rect); } else { - show(); + popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); } command_search_box->clear(); diff --git a/editor/editor_command_palette.h b/editor/editor_command_palette.h index 15200552b4..81cf401851 100644 --- a/editor/editor_command_palette.h +++ b/editor/editor_command_palette.h @@ -91,6 +91,7 @@ class EditorCommandPalette : public ConfirmationDialog { protected: static void _bind_methods(); + void _notification(int p_what); public: void open_popup(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 06e1e8b22e..463e8f6bdc 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -6038,14 +6038,7 @@ EditorNode::EditorNode() { Input *id = Input::get_singleton(); if (id) { - bool found_touchscreen = false; - for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) { - if (DisplayServer::get_singleton()->screen_is_touchscreen(i)) { - found_touchscreen = true; - } - } - - if (!found_touchscreen && Input::get_singleton()) { + if (!DisplayServer::get_singleton()->is_touchscreen_available() && Input::get_singleton()) { // Only if no touchscreen ui hint, disable emulation just in case. id->set_emulate_touch_from_mouse(false); } diff --git a/editor/editor_quick_open.cpp b/editor/editor_quick_open.cpp index bb533b88d6..6ddccba0e2 100644 --- a/editor/editor_quick_open.cpp +++ b/editor/editor_quick_open.cpp @@ -34,23 +34,24 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" -void EditorQuickOpen::popup_dialog(const String &p_base, bool p_enable_multi, bool p_dontclear) { +static Rect2i prev_rect = Rect2i(); +static bool was_showed = false; + +void EditorQuickOpen::popup_dialog(const String &p_base, bool p_enable_multi, bool p_dont_clear) { base_type = p_base; allow_multi_select = p_enable_multi; search_options->set_select_mode(allow_multi_select ? Tree::SELECT_MULTI : Tree::SELECT_SINGLE); - static bool was_showed = false; - if (!was_showed) { - was_showed = true; - popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); + if (was_showed) { + popup(prev_rect); } else { - show(); + popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f); } EditorFileSystemDirectory *efsd = EditorFileSystem::get_singleton()->get_filesystem(); _build_search_cache(efsd); - if (p_dontclear) { + if (p_dont_clear) { search_box->select_all(); _update_search(); } else { @@ -251,6 +252,13 @@ void EditorQuickOpen::_notification(int p_what) { search_box->set_clear_button_enabled(true); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible()) { + prev_rect = Rect2i(get_position(), get_size()); + was_showed = true; + } + } break; + case NOTIFICATION_EXIT_TREE: { disconnect("confirmed", callable_mp(this, &EditorQuickOpen::_confirmed)); } break; diff --git a/editor/editor_spin_slider.cpp b/editor/editor_spin_slider.cpp index 11a8fce9c3..9128143619 100644 --- a/editor/editor_spin_slider.cpp +++ b/editor/editor_spin_slider.cpp @@ -582,6 +582,7 @@ void EditorSpinSlider::_value_focus_exited() { //tab was pressed } else { //enter, click, esc + grab_focus(); } } diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 4273a31d62..c01db215da 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -442,10 +442,11 @@ HashSet<String> EditorExportPlatform::get_features(const Ref<EditorExportPreset> result.insert(E); } + result.insert("template"); if (p_debug) { - result.insert("debug"); + result.insert("template_debug"); } else { - result.insert("release"); + result.insert("template_release"); } if (!p_preset->get_custom_features().is_empty()) { diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 8df5808b11..6316a7f545 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1414,7 +1414,7 @@ void FileSystemDock::_update_dependencies_after_move(const HashMap<String, Strin Error err = ResourceLoader::rename_dependencies(file, p_renames); if (err == OK) { if (ResourceLoader::get_resource_type(file) == "PackedScene") { - EditorNode::get_singleton()->reload_scene(file); + callable_mp(EditorNode::get_singleton(), &EditorNode::reload_scene).bind(file).call_deferred(); } } else { EditorNode::get_singleton()->add_io_error(TTR("Unable to update dependencies:") + "\n" + remaps[i] + "\n"); diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index 666444eaf9..b7e7200b11 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -769,7 +769,7 @@ void FindInFilesPanel::draw_result_text(Object *item_obj, Rect2 rect) { Rect2 match_rect = rect; match_rect.position.x += font->get_string_size(item_text.left(r.begin_trimmed), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x - 1; - match_rect.size.x = font->get_string_size(_search_text_label->get_text(), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x + 2; + match_rect.size.x = font->get_string_size(_search_text_label->get_text(), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).x + 1; match_rect.position.y += 1 * EDSCALE; match_rect.size.y -= 2 * EDSCALE; diff --git a/editor/icons/OneWayTile.svg b/editor/icons/OneWayTile.svg new file mode 100644 index 0000000000..273b1a183b --- /dev/null +++ b/editor/icons/OneWayTile.svg @@ -0,0 +1 @@ +<svg clip-rule="evenodd" fill-rule="evenodd" height="16" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m10.958984 1.5-1.4785152 1.4667969-1.4785157 1.46875-1.4804687-1.4667969-1.4785156-1.4667969-.5214844.5136719-.5214844.5136719 2 1.9863281 2 1.984375 2-1.984375 2-1.9824219-.519531-.5175781zm0 8-1.4785152 1.466797-1.4785157 1.46875-1.4804687-1.466797-1.4785156-1.4667969-.5214844.5136719-.5214844.513672 2 1.986328 2 1.984375 2-1.984375 2-1.982422-.519531-.517578z" fill="#fff"/></svg> diff --git a/editor/icons/TileSelection.svg b/editor/icons/TileSelection.svg new file mode 100644 index 0000000000..418382aa1c --- /dev/null +++ b/editor/icons/TileSelection.svg @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="5" + viewBox="0 0 5 5" + width="5" + version="1.1" + id="svg10" + sodipodi:docname="TileSelection.svg" + inkscape:version="1.1 (c68e22c387, 2021-05-23)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs14"> + <linearGradient + id="linearGradient1060" + inkscape:swatch="solid"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop1058" /> + </linearGradient> + </defs> + <sodipodi:namedview + id="namedview12" + pagecolor="#505050" + bordercolor="#ffffff" + borderopacity="1" + inkscape:pageshadow="0" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="1" + showgrid="false" + inkscape:zoom="64" + inkscape:cx="4.3125" + inkscape:cy="1.984375" + inkscape:window-width="3840" + inkscape:window-height="2066" + inkscape:window-x="-11" + inkscape:window-y="-11" + inkscape:window-maximized="1" + inkscape:current-layer="svg10" /> + <rect + style="fill:none;stroke:#ffffff;stroke-width:1.00038;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect940" + width="3.9996195" + height="3.999619" + x="0.50019002" + y="0.50019002" /> + <rect + style="fill:none;stroke:#000000;stroke-width:0.999543;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect3062" + width="2.000457" + height="2.000457" + x="1.4997715" + y="1.4997715" /> +</svg> diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 0f67663948..dbd1b12a94 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -376,11 +376,11 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { undo_redo->commit_action(); } -void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position) { +void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_node_position) { _update_options_menu(p_has_input_ports); use_position_from_popup_menu = true; position_from_popup_menu = p_node_position; - add_node->get_popup()->set_position(p_popup_position); + add_node->get_popup()->set_position(graph->get_screen_position() + graph->get_local_mouse_position()); add_node->get_popup()->reset_size(); add_node->get_popup()->popup(); } @@ -390,7 +390,7 @@ void AnimationNodeBlendTreeEditor::_popup_request(const Vector2 &p_position) { return; } - _popup(false, graph->get_screen_position() + graph->get_local_mouse_position(), p_position); + _popup(false, p_position); } void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) { @@ -401,7 +401,7 @@ void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, in Ref<AnimationNode> node = blend_tree->get_node(p_from); if (node.is_valid()) { from_node = p_from; - _popup(true, p_release_position, graph->get_global_mouse_position()); + _popup(true, p_release_position); } } @@ -414,7 +414,7 @@ void AnimationNodeBlendTreeEditor::_connection_from_empty(const String &p_to, in if (node.is_valid()) { to_node = p_to; to_slot = p_to_slot; - _popup(false, p_release_position, graph->get_global_mouse_position()); + _popup(false, p_release_position); } } diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h index fb19cce147..4b55aa9b3f 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.h +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -117,7 +117,7 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { void _filter_toggled(); Ref<AnimationNode> _filter_edit; - void _popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position); + void _popup(bool p_has_input_ports, const Vector2 &p_node_position); void _popup_request(const Vector2 &p_position); void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position); diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index c5f7d2a047..586cd2bd59 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -54,60 +54,38 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p String entry_symbol = config->get_value("configuration", "entry_symbol"); - List<String> libraries; - - config->get_section_keys("libraries", &libraries); - - bool could_export = false; - for (const String &E : libraries) { - Vector<String> tags = E.split("."); - bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); - if (!p_features.has(tag)) { - all_tags_met = false; - break; - } - } - - if (all_tags_met) { - String library_path = config->get_value("libraries", E); - if (!library_path.begins_with("res://")) { - print_line("Skipping export of out-of-project library " + library_path); - continue; - } - add_shared_object(library_path, tags); - - if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { - String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" - "extern void add_ios_init_callback(void (*cb)());\n" - "\n" - "extern \"C\" void $ENTRY();\n" - "void $ENTRY_init() {\n" - " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" - "}\n" - "struct $ENTRY_struct {\n" - " $ENTRY_struct() {\n" - " add_ios_init_callback($ENTRY_init);\n" - " }\n" - "};\n" - "$ENTRY_struct $ENTRY_struct_instance;\n\n"; - additional_code = additional_code.replace("$ENTRY", entry_symbol); - add_ios_cpp_code(additional_code); - - String linker_flags = "-Wl,-U,_" + entry_symbol; - add_ios_linker_flags(linker_flags); - } - could_export = true; - break; + PackedStringArray tags; + String library_path = NativeExtension::find_extension_library( + p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); }, &tags); + if (!library_path.is_empty()) { + add_shared_object(library_path, tags); + + if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { + String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" + "extern void add_ios_init_callback(void (*cb)());\n" + "\n" + "extern \"C\" void $ENTRY();\n" + "void $ENTRY_init() {\n" + " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" + "}\n" + "struct $ENTRY_struct {\n" + " $ENTRY_struct() {\n" + " add_ios_init_callback($ENTRY_init);\n" + " }\n" + "};\n" + "$ENTRY_struct $ENTRY_struct_instance;\n\n"; + additional_code = additional_code.replace("$ENTRY", entry_symbol); + add_ios_cpp_code(additional_code); + + String linker_flags = "-Wl,-U,_" + entry_symbol; + add_ios_linker_flags(linker_flags); } - } - if (!could_export) { - Vector<String> tags; + } else { + Vector<String> features_vector; for (const String &E : p_features) { - tags.append(E); + features_vector.append(E); } - ERR_FAIL_MSG(vformat("No suitable library found. The libraries' tags referred to an invalid feature flag. Possible feature flags for your platform: %s", p_path, String(", ").join(tags))); + ERR_FAIL_MSG(vformat("No suitable library found for GDExtension: %s. Possible feature flags for your platform: %s", p_path, String(", ").join(features_vector))); } List<String> dependencies; @@ -115,11 +93,11 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p config->get_section_keys("dependencies", &dependencies); } - for (const String &E : libraries) { - Vector<String> tags = E.split("."); + for (const String &E : dependencies) { + Vector<String> dependency_tags = E.split("."); bool all_tags_met = true; - for (int i = 0; i < tags.size(); i++) { - String tag = tags[i].strip_edges(); + for (int i = 0; i < dependency_tags.size(); i++) { + String tag = dependency_tags[i].strip_edges(); if (!p_features.has(tag)) { all_tags_met = false; break; @@ -129,13 +107,12 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p if (all_tags_met) { Dictionary dependency = config->get_value("dependencies", E); for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) { - String library_path = *key; + String dependency_path = *key; String target_path = dependency[*key]; - if (!library_path.begins_with("res://")) { - print_line("Skipping export of out-of-project library " + library_path); - continue; + if (dependency_path.is_relative_path()) { + dependency_path = p_path.get_base_dir().path_join(dependency_path); } - add_shared_object(library_path, tags, target_path); + add_shared_object(dependency_path, dependency_tags, target_path); } break; } diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 0af2a13df2..c8b80db334 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -30,6 +30,7 @@ #include "node_3d_editor_gizmos.h" +#include "core/config/project_settings.h" #include "core/math/convex_hull.h" #include "core/math/geometry_2d.h" #include "core/math/geometry_3d.h" @@ -1732,6 +1733,24 @@ Camera3DGizmoPlugin::Camera3DGizmoPlugin() { create_handle_material("handles"); } +Size2i Camera3DGizmoPlugin::_get_viewport_size(Camera3D *p_camera) { + Viewport *viewport = p_camera->get_viewport(); + + Window *window = Object::cast_to<Window>(viewport); + if (window) { + return window->get_size(); + } + + SubViewport *sub_viewport = Object::cast_to<SubViewport>(viewport); + ERR_FAIL_NULL_V(sub_viewport, Size2i()); + + if (sub_viewport == EditorNode::get_singleton()->get_scene_root()) { + return Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); + } + + return sub_viewport->get_size(); +} + bool Camera3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to<Camera3D>(p_spatial) != nullptr; } @@ -1830,6 +1849,10 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Ref<Material> material = get_material("camera_material", p_gizmo); + const Size2i viewport_size = _get_viewport_size(camera); + const real_t viewport_aspect = viewport_size.x > 0 && viewport_size.y > 0 ? viewport_size.aspect() : 1.0; + const Size2 size_factor = viewport_aspect > 1.0 ? Size2(1.0, 1.0 / viewport_aspect) : Size2(viewport_aspect, 1.0); + #define ADD_TRIANGLE(m_a, m_b, m_c) \ { \ lines.push_back(m_a); \ @@ -1857,10 +1880,11 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { // The real FOV is halved for accurate representation float fov = camera->get_fov() / 2.0; - Vector3 side = Vector3(Math::sin(Math::deg_to_rad(fov)), 0, -Math::cos(Math::deg_to_rad(fov))); - Vector3 nside = side; - nside.x = -nside.x; - Vector3 up = Vector3(0, side.x, 0); + const float hsize = Math::sin(Math::deg_to_rad(fov)); + const float depth = -Math::cos(Math::deg_to_rad(fov)); + Vector3 side = Vector3(hsize * size_factor.x, 0, depth); + Vector3 nside = Vector3(-side.x, side.y, side.z); + Vector3 up = Vector3(0, hsize * size_factor.y, 0); ADD_TRIANGLE(Vector3(), side + up, side - up); ADD_TRIANGLE(Vector3(), nside + up, nside - up); @@ -1868,18 +1892,18 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ADD_TRIANGLE(Vector3(), side - up, nside - up); handles.push_back(side); - side.x *= 0.25; - nside.x *= 0.25; - Vector3 tup(0, up.y * 3 / 2, side.z); + side.x = MIN(side.x, hsize * 0.25); + nside.x = -side.x; + Vector3 tup(0, up.y + hsize / 2, side.z); ADD_TRIANGLE(tup, side + up, nside + up); - } break; + case Camera3D::PROJECTION_ORTHOGONAL: { float size = camera->get_size(); float hsize = size * 0.5; - Vector3 right(hsize, 0, 0); - Vector3 up(0, hsize, 0); + Vector3 right(hsize * size_factor.x, 0, 0); + Vector3 up(0, hsize * size_factor.y, 0); Vector3 back(0, 0, -1.0); Vector3 front(0, 0, 0); @@ -1890,18 +1914,19 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { handles.push_back(right + back); - right.x *= 0.25; - Vector3 tup(0, up.y * 3 / 2, back.z); + right.x = MIN(right.x, hsize * 0.25); + Vector3 tup(0, up.y + hsize / 2, back.z); ADD_TRIANGLE(tup, right + up + back, -right + up + back); } break; + case Camera3D::PROJECTION_FRUSTUM: { float hsize = camera->get_size() / 2.0; Vector3 side = Vector3(hsize, 0, -camera->get_near()).normalized(); - Vector3 nside = side; - nside.x = -nside.x; - Vector3 up = Vector3(0, side.x, 0); + side.x *= size_factor.x; + Vector3 nside = Vector3(-side.x, side.y, side.z); + Vector3 up = Vector3(0, hsize * size_factor.y, 0); Vector3 offset = Vector3(camera->get_frustum_offset().x, camera->get_frustum_offset().y, 0.0); ADD_TRIANGLE(Vector3(), side + up + offset, side - up + offset); @@ -1909,11 +1934,11 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ADD_TRIANGLE(Vector3(), side + up + offset, nside + up + offset); ADD_TRIANGLE(Vector3(), side - up + offset, nside - up + offset); - side.x *= 0.25; - nside.x *= 0.25; - Vector3 tup(0, up.y * 3 / 2, side.z); + side.x = MIN(side.x, hsize * 0.25); + nside.x = -side.x; + Vector3 tup(0, up.y + hsize / 2, side.z); ADD_TRIANGLE(tup + offset, side + up + offset, nside + up + offset); - } + } break; } #undef ADD_TRIANGLE @@ -1921,7 +1946,10 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_lines(lines, material); p_gizmo->add_collision_segments(lines); - p_gizmo->add_handles(handles, get_material("handles")); + + if (!handles.is_empty()) { + p_gizmo->add_handles(handles, get_material("handles")); + } } ////// diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index d7e3e03f61..60d44ad787 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -264,6 +264,9 @@ public: class Camera3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(Camera3DGizmoPlugin, EditorNode3DGizmoPlugin); +private: + static Size2i _get_viewport_size(Camera3D *p_camera); + public: bool has_gizmo(Node3D *p_spatial) override; String get_gizmo_name() const override; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index c97de80a76..112a3fa51b 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -7477,6 +7477,8 @@ void Node3DEditor::_notification(int p_what) { sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size()); environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size()); + + EditorNode::get_singleton()->connect("project_settings_changed", callable_mp(this, &Node3DEditor::update_all_gizmos).bind(Variant())); } break; case NOTIFICATION_ENTER_TREE: { @@ -8420,7 +8422,7 @@ Node3DEditor::Node3DEditor() { EDITOR_DEF("editors/3d/manipulator_gizmo_opacity", 0.9); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::FLOAT, "editors/3d/manipulator_gizmo_opacity", PROPERTY_HINT_RANGE, "0,1,0.01")); EDITOR_DEF_RST("editors/3d/navigation/show_viewport_rotation_gizmo", true); - EDITOR_DEF_RST("editors/3d/navigation/show_viewport_navigation_gizmo", DisplayServer::get_singleton()->screen_is_touchscreen()); + EDITOR_DEF_RST("editors/3d/navigation/show_viewport_navigation_gizmo", DisplayServer::get_singleton()->is_touchscreen_available()); current_hover_gizmo_handle = -1; current_hover_gizmo_handle_secondary = false; diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 5a7b0321b7..63ca78d6c0 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -240,38 +240,63 @@ void Path3DGizmo::redraw() { return; } - Vector<Vector3> v3a = c->tessellate(); - //Vector<Vector3> v3a=c->get_baked_points(); + real_t interval = 0.1; + const real_t length = c->get_baked_length(); - int v3s = v3a.size(); - if (v3s == 0) { - return; - } - Vector<Vector3> v3p; - const Vector3 *r = v3a.ptr(); - - // BUG: the following won't work when v3s, avoid drawing as a temporary workaround. - for (int i = 0; i < v3s - 1; i++) { - v3p.push_back(r[i]); - v3p.push_back(r[i + 1]); - //v3p.push_back(r[i]); - //v3p.push_back(r[i]+Vector3(0,0.2,0)); - } + // 1. Draw curve and bones. + if (length > CMP_EPSILON) { + const int sample_count = int(length / interval) + 2; + interval = length / (sample_count - 1); // Recalculate real interval length. + + Vector<Transform3D> frames; + frames.resize(sample_count); + + { + Transform3D *w = frames.ptrw(); + + for (int i = 0; i < sample_count; i++) { + w[i] = c->sample_baked_with_rotation(i * interval, true, true); + } + } + + const Transform3D *r = frames.ptr(); + Vector<Vector3> v3p; + for (int i = 0; i < sample_count - 1; i++) { + const Vector3 p1 = r[i].origin; + const Vector3 p2 = r[i + 1].origin; + const Vector3 side = r[i].basis.get_column(0); + const Vector3 up = r[i].basis.get_column(1); + const Vector3 forward = r[i].basis.get_column(2); + + // Curve segment. + v3p.push_back(p1); + v3p.push_back(p2); + + // Fish Bone. + v3p.push_back(p1); + v3p.push_back(p1 + (side - forward) * 0.06); + + v3p.push_back(p1); + v3p.push_back(p1 + (-side - forward) * 0.06); + + v3p.push_back(p1); + v3p.push_back(p1 + up * 0.03); + } - if (v3p.size() > 1) { add_lines(v3p, path_material); add_collision_segments(v3p); } + // 2. Draw handles. if (Path3DEditorPlugin::singleton->get_edited_path() == path) { - v3p.clear(); + Vector<Vector3> v3p; Vector<Vector3> handle_points; Vector<Vector3> sec_handle_points; for (int i = 0; i < c->get_point_count(); i++) { Vector3 p = c->get_point_position(i); handle_points.push_back(p); - // push Out points first so they get selected if the In and Out points are on top of each other. + // Push out points first so they get selected if the In and Out points are on top of each other. if (i < c->get_point_count() - 1) { v3p.push_back(p); v3p.push_back(p + c->get_point_out(i)); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 747fdfd041..38639ac811 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1223,7 +1223,9 @@ void ScriptTextEditor::_edit_option(int p_op) { code_editor->duplicate_selection(); } break; case EDIT_TOGGLE_FOLD_LINE: { - tx->toggle_foldable_line(tx->get_caret_line()); + for (int caret_idx = 0; caret_idx < tx->get_caret_count(); caret_idx++) { + tx->toggle_foldable_line(tx->get_caret_line(caret_idx)); + } tx->queue_redraw(); } break; case EDIT_FOLD_ALL_LINES: { @@ -1291,28 +1293,28 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case EDIT_EVALUATE: { Expression expression; - Vector<String> lines = code_editor->get_text_editor()->get_selected_text().split("\n"); - PackedStringArray results; - - for (int i = 0; i < lines.size(); i++) { - String line = lines[i]; - String whitespace = line.substr(0, line.size() - line.strip_edges(true, false).size()); //extract the whitespace at the beginning - - if (expression.parse(line) == OK) { - Variant result = expression.execute(Array(), Variant(), false, true); - if (expression.get_error_text().is_empty()) { - results.push_back(whitespace + result.get_construct_string()); + tx->begin_complex_operation(); + for (int caret_idx = 0; caret_idx < tx->get_caret_count(); caret_idx++) { + Vector<String> lines = tx->get_selected_text(caret_idx).split("\n"); + PackedStringArray results; + + for (int i = 0; i < lines.size(); i++) { + String line = lines[i]; + String whitespace = line.substr(0, line.size() - line.strip_edges(true, false).size()); // Extract the whitespace at the beginning. + if (expression.parse(line) == OK) { + Variant result = expression.execute(Array(), Variant(), false, true); + if (expression.get_error_text().is_empty()) { + results.push_back(whitespace + result.get_construct_string()); + } else { + results.push_back(line); + } } else { results.push_back(line); } - } else { - results.push_back(line); } + tx->insert_text_at_caret(String("\n").join(results), caret_idx); } - - code_editor->get_text_editor()->begin_complex_operation(); //prevents creating a two-step undo - code_editor->get_text_editor()->insert_text_at_caret(String("\n").join(results)); - code_editor->get_text_editor()->end_complex_operation(); + tx->end_complex_operation(); } break; case SEARCH_FIND: { code_editor->get_find_replace_bar()->popup_search(); @@ -1327,14 +1329,14 @@ void ScriptTextEditor::_edit_option(int p_op) { code_editor->get_find_replace_bar()->popup_replace(); } break; case SEARCH_IN_FILES: { - String selected_text = code_editor->get_text_editor()->get_selected_text(); + String selected_text = tx->get_selected_text(); // Yep, because it doesn't make sense to instance this dialog for every single script open... // So this will be delegated to the ScriptEditor. emit_signal(SNAME("search_in_files_requested"), selected_text); } break; case REPLACE_IN_FILES: { - String selected_text = code_editor->get_text_editor()->get_selected_text(); + String selected_text = tx->get_selected_text(); emit_signal(SNAME("replace_in_files_requested"), selected_text); } break; @@ -1358,10 +1360,12 @@ void ScriptTextEditor::_edit_option(int p_op) { code_editor->remove_all_bookmarks(); } break; case DEBUG_TOGGLE_BREAKPOINT: { - int line = tx->get_caret_line(); - bool dobreak = !tx->is_line_breakpointed(line); - tx->set_line_as_breakpoint(line, dobreak); - EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), line + 1, dobreak); + for (int caret_idx = 0; caret_idx < tx->get_caret_count(); caret_idx++) { + int line = tx->get_caret_line(caret_idx); + bool dobreak = !tx->is_line_breakpointed(line); + tx->set_line_as_breakpoint(line, dobreak); + EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), line + 1, dobreak); + } } break; case DEBUG_REMOVE_ALL_BREAKPOINTS: { PackedInt32Array bpoints = tx->get_breakpointed_lines(); @@ -1379,26 +1383,14 @@ void ScriptTextEditor::_edit_option(int p_op) { return; } - tx->remove_secondary_carets(); - int line = tx->get_caret_line(); - - // wrap around - if (line >= (int)bpoints[bpoints.size() - 1]) { - tx->unfold_line(bpoints[0]); - tx->set_caret_line(bpoints[0]); - tx->center_viewport_to_caret(); - } else { - for (int i = 0; i < bpoints.size(); i++) { - int bline = bpoints[i]; - if (bline > line) { - tx->unfold_line(bline); - tx->set_caret_line(bline); - tx->center_viewport_to_caret(); - return; - } + int current_line = tx->get_caret_line(); + int bpoint_idx = 0; + if (current_line < (int)bpoints[bpoints.size() - 1]) { + while (bpoint_idx < bpoints.size() && bpoints[bpoint_idx] <= current_line) { + bpoint_idx++; } } - + code_editor->goto_line_centered(bpoints[bpoint_idx]); } break; case DEBUG_GOTO_PREV_BREAKPOINT: { PackedInt32Array bpoints = tx->get_breakpointed_lines(); @@ -1406,25 +1398,14 @@ void ScriptTextEditor::_edit_option(int p_op) { return; } - tx->remove_secondary_carets(); - int line = tx->get_caret_line(); - // wrap around - if (line <= (int)bpoints[0]) { - tx->unfold_line(bpoints[bpoints.size() - 1]); - tx->set_caret_line(bpoints[bpoints.size() - 1]); - tx->center_viewport_to_caret(); - } else { - for (int i = bpoints.size() - 1; i >= 0; i--) { - int bline = bpoints[i]; - if (bline < line) { - tx->unfold_line(bline); - tx->set_caret_line(bline); - tx->center_viewport_to_caret(); - return; - } + int current_line = tx->get_caret_line(); + int bpoint_idx = bpoints.size() - 1; + if (current_line > (int)bpoints[0]) { + while (bpoint_idx >= 0 && bpoints[bpoint_idx] >= current_line) { + bpoint_idx--; } } - + code_editor->goto_line_centered(bpoints[bpoint_idx]); } break; case HELP_CONTEXTUAL: { String text = tx->get_selected_text(0); @@ -1835,7 +1816,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { base = _find_node_for_script(base, base, script); } ScriptLanguage::LookupResult result; - if (script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), word_at_pos, script->get_path(), base, result) == OK) { + if (script->get_language()->lookup_code(tx->get_text_for_symbol_lookup(), word_at_pos, script->get_path(), base, result) == OK) { open_docs = true; } } diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 1ae419053e..4f0894a1a9 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -96,6 +96,7 @@ protected: static void _bind_methods(); public: + virtual String get_name() const override { return "Shader"; } virtual void edit(Object *p_object) override; virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index baf5e363f8..d6079d6285 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -353,7 +353,9 @@ void TextEditor::_edit_option(int p_op) { code_editor->duplicate_selection(); } break; case EDIT_TOGGLE_FOLD_LINE: { - tx->toggle_foldable_line(tx->get_caret_line()); + for (int caret_idx = 0; caret_idx < tx->get_caret_count(); caret_idx++) { + tx->toggle_foldable_line(tx->get_caret_line(caret_idx)); + } tx->queue_redraw(); } break; case EDIT_FOLD_ALL_LINES: { diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp index e266d26b73..f892f3637d 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.cpp +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -236,7 +236,7 @@ void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) { if (atlas_source.is_valid()) { Ref<Texture2D> texture = atlas_source->get_texture(); if (texture.is_valid()) { - String item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); + String item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id); atlas_merging_atlases_list->add_item(item_text, texture); atlas_merging_atlases_list->set_item_metadata(-1, source_id); } diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 993f606f2f..147bb5f4e9 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -1603,12 +1603,31 @@ void TileDataCollisionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transfor } RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + + Ref<Texture2D> one_way_icon = get_theme_icon(SNAME("OneWayTile"), SNAME("EditorIcons")); for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { Vector<Vector2> polygon = tile_data->get_collision_polygon_points(physics_layer, i); - if (polygon.size() >= 3) { - p_canvas_item->draw_polygon(polygon, color); + if (polygon.size() < 3) { + continue; + } + + p_canvas_item->draw_polygon(polygon, color); + + if (tile_data->is_collision_polygon_one_way(physics_layer, i)) { + PackedVector2Array uvs; + uvs.resize(polygon.size()); + Vector2 size_1 = Vector2(1, 1) / tile_set->get_tile_size(); + + for (int j = 0; j < polygon.size(); j++) { + uvs.write[j] = polygon[j] * size_1 + Vector2(0.5, 0.5); + } + + Vector<Color> color2; + color2.push_back(Color(1, 1, 1, 0.4)); + p_canvas_item->draw_polygon(polygon, color2, uvs, one_way_icon); } } + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); } diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index e622a0817a..0331e3f69e 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -156,7 +156,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { // Common to all type of sources. if (!source->get_name().is_empty()) { - item_text = vformat(TTR("%s (id:%d)"), source->get_name(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), source->get_name(), source_id); } // Atlas source. @@ -165,7 +165,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { texture = atlas_source->get_texture(); if (item_text.is_empty()) { if (texture.is_valid()) { - item_text = vformat("%s (ID: %d)", texture->get_path().get_file(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id); } else { item_text = vformat(TTR("No Texture Atlas Source (ID: %d)"), source_id); } @@ -472,6 +472,7 @@ void TileMapEditorTilesPlugin::_update_theme() { random_tile_toggle->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("RandomNumberGenerator"), SNAME("EditorIcons"))); missing_atlas_texture_icon = tiles_bottom_panel->get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); + _update_tile_set_sources_list(); } bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { @@ -1164,7 +1165,7 @@ HashMap<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vecto } // Get surrounding tiles (handles different tile shapes). - TypedArray<Vector2i> around = tile_map->get_surrounding_tiles(coords); + TypedArray<Vector2i> around = tile_map->get_surrounding_cells(coords); for (int i = 0; i < around.size(); i++) { to_check.push_back(around[i]); } @@ -1697,7 +1698,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { if (frame > 0) { color.a *= 0.3; } - tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E.get_atlas_coords(), frame), color, false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, atlas->get_tile_texture_region(E.get_atlas_coords(), frame), color); } } } @@ -1705,11 +1706,8 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { // Draw the hovered tile. if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) { for (int frame = 0; frame < atlas->get_tile_animation_frames_count(hovered_tile.get_atlas_coords()); frame++) { - Color color = Color(1.0, 1.0, 1.0); - if (frame > 0) { - color.a *= 0.3; - } - tile_atlas_control->draw_rect(atlas->get_tile_texture_region(hovered_tile.get_atlas_coords(), frame), color, false); + Color color = Color(1.0, 0.8, 0.0, frame == 0 ? 0.6 : 0.3); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, atlas->get_tile_texture_region(hovered_tile.get_atlas_coords(), frame), color); } } @@ -1730,9 +1728,8 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { } } } - Color selection_rect_color = selection_color.lightened(0.2); for (const Vector2i &E : to_draw) { - tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E), selection_rect_color, false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, atlas->get_tile_texture_region(E)); } } } @@ -1881,7 +1878,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() { if (E.source_id == source_id && E.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && E.alternative_tile > 0) { Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E.get_atlas_coords(), E.alternative_tile); if (rect != Rect2i()) { - alternative_tiles_control->draw_rect(rect, Color(0.2, 0.2, 1.0), false); + TilesEditorPlugin::draw_selection_rect(alternative_tiles_control, rect); } } } @@ -1890,7 +1887,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() { if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile > 0) { Rect2i rect = tile_atlas_view->get_alternative_tile_rect(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile); if (rect != Rect2i()) { - alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false); + TilesEditorPlugin::draw_selection_rect(alternative_tiles_control, rect, Color(1.0, 0.8, 0.0, 0.5)); } } } @@ -2547,7 +2544,7 @@ RBSet<Vector2i> TileMapEditorTerrainsPlugin::_get_cells_for_bucket_fill(Vector2i output.insert(coords); // Get surrounding tiles (handles different tile shapes). - TypedArray<Vector2i> around = tile_map->get_surrounding_tiles(coords); + TypedArray<Vector2i> around = tile_map->get_surrounding_cells(coords); for (int i = 0; i < around.size(); i++) { to_check.push_back(around[i]); } diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp index 7058b28e68..b31fb1aa58 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -36,7 +36,7 @@ #include "editor/editor_undo_redo_manager.h" #include "scene/gui/separator.h" -void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list, MouseButton p_mouse_button_index) { +void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index, Object *p_item_list) { if (p_mouse_button_index != MouseButton::RIGHT) { return; } @@ -77,7 +77,7 @@ void TileProxiesManagerDialog::_delete_selected_bindings() { Vector<int> alternative_level_selected = alternative_level_list->get_selected_items(); for (int i = 0; i < alternative_level_selected.size(); i++) { Array key = alternative_level_list->get_item_metadata(alternative_level_selected[i]); - Array val = tile_set->get_coords_level_tile_proxy(key[0], key[1]); + Array val = tile_set->get_alternative_level_tile_proxy(key[0], key[1], key[2]); undo_redo->add_do_method(*tile_set, "remove_alternative_level_tile_proxy", key[0], key[1], key[2]); undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", key[0], key[1], key[2], val[0], val[1], val[2]); } diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.h b/editor/plugins/tiles/tile_proxies_manager_dialog.h index e2363eb809..09c8068336 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.h +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.h @@ -61,7 +61,7 @@ private: EditorPropertyInteger *alternative_to_property_editor = nullptr; PopupMenu *popup_menu = nullptr; - void _right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list, MouseButton p_mouse_button_index); + void _right_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index, Object *p_item_list); void _menu_id_pressed(int p_id); void _delete_selected_bindings(); void _update_lists(); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 7ed84423bc..524a1fa38a 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -1684,10 +1684,6 @@ Array TileSetAtlasSourceEditor::_get_selection_as_array() { } void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { - // Colors. - Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); - Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); - // Draw the selected tile. if (tools_button_group->get_pressed_button() == tool_select_button) { for (const TileSelection &E : selection) { @@ -1695,12 +1691,9 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { if (selected.alternative == 0) { // Draw the rect. for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(selected.tile); frame++) { - Color color = selection_color; - if (frame > 0) { - color.a *= 0.3; - } + Color color = Color(0.0, 1.0, 0.0, frame == 0 ? 1.0 : 0.3); Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile, frame); - tile_atlas_control->draw_rect(region, color, false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, region, color); } } } @@ -1742,7 +1735,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { // Draw the tiles to be removed. for (const Vector2i &E : drag_modified_tiles) { for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(E); frame++) { - tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E, frame), Color(0.0, 0.0, 0.0), false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, tile_set_atlas_source->get_tile_texture_region(E, frame), Color(0.0, 0.0, 0.0)); } } } else if (drag_type == DRAG_TYPE_RECT_SELECT || drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT) { @@ -1754,7 +1747,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Color color = Color(0.0, 0.0, 0.0); if (drag_type == DRAG_TYPE_RECT_SELECT) { - color = selection_color.lightened(0.2); + color = Color(1.0, 1.0, 0.0); } RBSet<Vector2i> to_paint; @@ -1769,7 +1762,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { for (const Vector2i &E : to_paint) { Vector2i coords = E; - tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(coords), color, false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, tile_set_atlas_source->get_tile_texture_region(coords), color); } } else if (drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) { // Draw tiles to be created. @@ -1786,7 +1779,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Vector2i coords = Vector2i(x, y); if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { Vector2i origin = margins + (coords * (tile_size + separation)); - tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, Rect2i(origin, tile_size)); } } } @@ -1803,7 +1796,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); Vector2i origin = margins + (area.position * (tile_size + separation)); - tile_atlas_control->draw_rect(Rect2i(origin, area.size * tile_size), Color(1.0, 1.0, 1.0), false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, Rect2i(origin, area.size * tile_size)); } else { Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); if (hovered_base_tile_coords.x >= 0 && hovered_base_tile_coords.y >= 0 && hovered_base_tile_coords.x < grid_size.x && hovered_base_tile_coords.y < grid_size.y) { @@ -1811,11 +1804,8 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { if (hovered_tile != TileSetSource::INVALID_ATLAS_COORDS) { // Draw existing hovered tile. for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(hovered_tile); frame++) { - Color color = Color(1.0, 1.0, 1.0); - if (frame > 0) { - color.a *= 0.3; - } - tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile, frame), color, false); + Color color = Color(1.0, 0.8, 0.0, frame == 0 ? 0.6 : 0.3); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, tile_set_atlas_source->get_tile_texture_region(hovered_tile, frame), color); } } else { // Draw empty tile, only in add/remove tiles mode. @@ -1824,7 +1814,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); Vector2i origin = margins + (hovered_base_tile_coords * (tile_size + separation)); - tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false); + TilesEditorPlugin::draw_selection_rect(tile_atlas_control, Rect2i(origin, tile_size)); } } } @@ -1976,9 +1966,6 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited() { } void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() { - Color grid_color = EDITOR_GET("editors/tiles_editor/grid_color"); - Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); - // Update the hovered alternative tile. if (tools_button_group->get_pressed_button() == tool_select_button) { // Draw hovered tile. @@ -1986,7 +1973,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() { if (coords != TileSetSource::INVALID_ATLAS_COORDS) { Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, hovered_alternative_tile_coords.z); if (rect != Rect2i()) { - alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false); + TilesEditorPlugin::draw_selection_rect(alternative_tiles_control, rect, Color(1.0, 0.8, 0.0, 0.5)); } } @@ -1996,7 +1983,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() { if (selected.alternative >= 1) { Rect2i rect = tile_atlas_view->get_alternative_tile_rect(selected.tile, selected.alternative); if (rect != Rect2i()) { - alternative_tiles_control->draw_rect(rect, selection_color, false); + TilesEditorPlugin::draw_selection_rect(alternative_tiles_control, rect); } } } diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index b24c5059b0..934de05cfb 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -151,7 +151,7 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { // Common to all type of sources. if (!source->get_name().is_empty()) { - item_text = vformat(TTR("%s (id:%d)"), source->get_name(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), source->get_name(), source_id); } // Atlas source. @@ -160,7 +160,7 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { texture = atlas_source->get_texture(); if (item_text.is_empty()) { if (texture.is_valid()) { - item_text = vformat("%s (ID: %d)", texture->get_path().get_file(), source_id); + item_text = vformat(TTR("%s (ID: %d)"), texture->get_path().get_file(), source_id); } else { item_text = vformat(TTR("No Texture Atlas Source (ID: %d)"), source_id); } @@ -346,6 +346,7 @@ void TileSetEditor::_notification(int p_what) { source_sort_button->set_icon(get_theme_icon(SNAME("Sort"), SNAME("EditorIcons"))); sources_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); missing_texture_texture = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); + _update_sources_list(); } break; case NOTIFICATION_INTERNAL_PROCESS: { diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 5d93f58f34..ee29913334 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -385,6 +385,15 @@ bool TilesEditorPlugin::handles(Object *p_object) const { return p_object->is_class("TileMap") || p_object->is_class("TileSet"); } +void TilesEditorPlugin::draw_selection_rect(CanvasItem *p_ci, const Rect2 &p_rect, const Color &p_color) { + real_t scale = p_ci->get_global_transform().get_scale().x * 0.5; + p_ci->draw_set_transform(p_rect.position, 0, Vector2(1, 1) / scale); + RS::get_singleton()->canvas_item_add_nine_patch( + p_ci->get_canvas_item(), Rect2(Vector2(), p_rect.size * scale), Rect2(), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("TileSelection"), SNAME("EditorIcons"))->get_rid(), + Vector2(2, 2), Vector2(2, 2), RS::NINE_PATCH_STRETCH, RS::NINE_PATCH_STRETCH, false, p_color); + p_ci->draw_set_transform_matrix(Transform2D()); +} + TilesEditorPlugin::TilesEditorPlugin() { set_process_internal(true); diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h index fe0d8179bc..825a10dac2 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.h +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -128,6 +128,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; + static void draw_selection_rect(CanvasItem *p_ci, const Rect2 &p_rect, const Color &p_color = Color(1.0, 1.0, 1.0)); + TilesEditorPlugin(); ~TilesEditorPlugin(); }; diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index 86aa897c78..369a59c443 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -988,7 +988,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { metadata_dialog->set_title(TTR("Create Version Control Metadata")); metadata_dialog->set_min_size(Size2(200, 40)); metadata_dialog->get_ok_button()->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_create_vcs_metadata_files)); - version_control_actions->add_child(metadata_dialog); + add_child(metadata_dialog); VBoxContainer *metadata_vb = memnew(VBoxContainer); metadata_dialog->add_child(metadata_vb); @@ -1017,7 +1017,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { set_up_dialog->set_min_size(Size2(600, 100)); set_up_dialog->add_cancel_button("Cancel"); set_up_dialog->set_hide_on_ok(true); - version_control_actions->add_child(set_up_dialog); + add_child(set_up_dialog); Button *set_up_apply_button = set_up_dialog->get_ok_button(); set_up_apply_button->set_text(TTR("Apply")); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 9990d5c06f..cf811067c9 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -269,6 +269,19 @@ void VisualShaderGraphPlugin::set_expression(VisualShader::Type p_type, int p_no links[p_node_id].expression_edit->set_text(p_expression); } +Ref<Script> VisualShaderGraphPlugin::get_node_script(int p_node_id) const { + if (!links.has(p_node_id)) { + return Ref<Script>(); + } + + Ref<VisualShaderNodeCustom> custom = Ref<VisualShaderNodeCustom>(links[p_node_id].visual_node); + if (custom.is_valid()) { + return custom->get_script(); + } + + return Ref<Script>(); +} + void VisualShaderGraphPlugin::update_node_size(int p_node_id) { if (!links.has(p_node_id)) { return; @@ -1137,10 +1150,6 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { } } -void VisualShaderEditor::update_nodes() { - _update_nodes(); -} - void VisualShaderEditor::add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin) { if (plugins.has(p_plugin)) { return; @@ -1202,6 +1211,228 @@ void VisualShaderEditor::add_custom_type(const String &p_name, const Ref<Script> add_options.push_back(ao); } +Dictionary VisualShaderEditor::get_custom_node_data(Ref<VisualShaderNodeCustom> &p_custom_node) { + Dictionary dict; + dict["script"] = p_custom_node->get_script(); + + String name; + if (p_custom_node->has_method("_get_name")) { + name = (String)p_custom_node->call("_get_name"); + } else { + name = "Unnamed"; + } + dict["name"] = name; + + String description = ""; + if (p_custom_node->has_method("_get_description")) { + description = (String)p_custom_node->call("_get_description"); + } + dict["description"] = description; + + int return_icon_type = -1; + if (p_custom_node->has_method("_get_return_icon_type")) { + return_icon_type = (int)p_custom_node->call("_get_return_icon_type"); + } + dict["return_icon_type"] = return_icon_type; + + String category = ""; + if (p_custom_node->has_method("_get_category")) { + category = (String)p_custom_node->call("_get_category"); + } + category = category.rstrip("/"); + category = category.lstrip("/"); + category = "Addons/" + category; + + String subcategory = ""; + if (p_custom_node->has_method("_get_subcategory")) { + subcategory = (String)p_custom_node->call("_get_subcategory"); + } + if (!subcategory.is_empty()) { + category += "/" + subcategory; + } + dict["category"] = category; + + bool highend = false; + if (p_custom_node->has_method("_is_highend")) { + highend = (bool)p_custom_node->call("_is_highend"); + } + dict["highend"] = highend; + + return dict; +} + +void VisualShaderEditor::update_custom_type(const Ref<Resource> &p_resource) { + Ref<Script> scr = Ref<Script>(p_resource.ptr()); + if (scr.is_null() || scr->get_instance_base_type() != String("VisualShaderNodeCustom")) { + return; + } + + Ref<VisualShaderNodeCustom> ref; + ref.instantiate(); + ref->set_script(scr); + if (!ref->is_available(visual_shader->get_mode(), visual_shader->get_shader_type())) { + for (int i = 0; i < add_options.size(); i++) { + if (add_options[i].is_custom && add_options[i].script == scr) { + add_options.remove_at(i); + _update_options_menu(); + // TODO: Make indication for the existed custom nodes with that script on graph to be disabled. + break; + } + } + return; + } + Dictionary dict = get_custom_node_data(ref); + + bool found_type = false; + bool need_rebuild = false; + + for (int i = 0; i < add_options.size(); i++) { + if (add_options[i].is_custom && add_options[i].script == scr) { + found_type = true; + + add_options.write[i].name = dict["name"]; + add_options.write[i].return_type = dict["return_icon_type"]; + add_options.write[i].description = dict["description"]; + add_options.write[i].category = dict["category"]; + add_options.write[i].highend = dict["highend"]; + + int max_type = 0; + int type_offset = 0; + switch (visual_shader->get_mode()) { + case Shader::MODE_CANVAS_ITEM: + case Shader::MODE_SPATIAL: { + max_type = 3; + } break; + case Shader::MODE_PARTICLES: { + max_type = 5; + type_offset = 3; + } break; + case Shader::MODE_SKY: { + max_type = 1; + type_offset = 8; + } break; + case Shader::MODE_FOG: { + max_type = 1; + type_offset = 9; + } break; + default: { + } break; + } + max_type = type_offset + max_type; + + for (int t = type_offset; t < max_type; t++) { + VisualShader::Type type = (VisualShader::Type)t; + Vector<int> nodes = visual_shader->get_node_list(type); + + List<VisualShader::Connection> node_connections; + visual_shader->get_node_connections(type, &node_connections); + + List<VisualShader::Connection> custom_node_input_connections; + List<VisualShader::Connection> custom_node_output_connections; + for (const VisualShader::Connection &E : node_connections) { + int from = E.from_node; + int from_idx = E.from_port; + int to = E.to_node; + int to_idx = E.to_port; + + if (graph_plugin->get_node_script(from) == scr) { + custom_node_output_connections.push_back({ from, from_idx, to, to_idx }); + } else if (graph_plugin->get_node_script(to) == scr) { + custom_node_input_connections.push_back({ from, from_idx, to, to_idx }); + } + } + + for (int j = 0; j < nodes.size(); j++) { + int node_id = nodes[j]; + + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, node_id); + if (vsnode.is_null()) { + continue; + } + Ref<VisualShaderNodeCustom> custom_node = Ref<VisualShaderNodeCustom>(vsnode.ptr()); + if (custom_node.is_null() || custom_node->get_script() != scr) { + continue; + } + need_rebuild = true; + + // Removes invalid connections. + { + int prev_input_port_count = custom_node->get_input_port_count(); + int prev_output_port_count = custom_node->get_output_port_count(); + + custom_node->update_ports(); + + int input_port_count = custom_node->get_input_port_count(); + int output_port_count = custom_node->get_output_port_count(); + + if (output_port_count != prev_output_port_count) { + for (const VisualShader::Connection &E : custom_node_output_connections) { + int from = E.from_node; + int from_idx = E.from_port; + int to = E.to_node; + int to_idx = E.to_port; + + if (from_idx >= output_port_count) { + visual_shader->disconnect_nodes(type, from, from_idx, to, to_idx); + graph_plugin->disconnect_nodes(type, from, from_idx, to, to_idx); + } + } + } + if (input_port_count != prev_input_port_count) { + for (const VisualShader::Connection &E : custom_node_input_connections) { + int from = E.from_node; + int from_idx = E.from_port; + int to = E.to_node; + int to_idx = E.to_port; + + if (to_idx >= input_port_count) { + visual_shader->disconnect_nodes(type, from, from_idx, to, to_idx); + graph_plugin->disconnect_nodes(type, from, from_idx, to, to_idx); + } + } + } + } + + graph_plugin->update_node(type, node_id); + } + } + break; + } + } + + if (!found_type) { + add_custom_type(dict["name"], dict["script"], dict["description"], dict["return_icon_type"], dict["category"], dict["highend"]); + } + + // To prevent updating options multiple times when multiple scripts are saved. + if (!_block_update_options_menu) { + _block_update_options_menu = true; + + call_deferred(SNAME("_update_options_menu_deferred")); + } + + // To prevent rebuilding the shader multiple times when multiple scripts are saved. + if (need_rebuild && !_block_rebuild_shader) { + _block_rebuild_shader = true; + + call_deferred(SNAME("_rebuild_shader_deferred")); + } +} + +void VisualShaderEditor::_update_options_menu_deferred() { + _update_options_menu(); + + _block_update_options_menu = false; +} + +void VisualShaderEditor::_rebuild_shader_deferred() { + if (visual_shader.is_valid()) { + visual_shader->rebuild(); + } + + _block_rebuild_shader = false; +} + bool VisualShaderEditor::_is_available(int p_mode) { int current_mode = edit_type->get_selected(); @@ -1243,57 +1474,10 @@ void VisualShaderEditor::_update_nodes() { if (!ref->is_available(visual_shader->get_mode(), visual_shader->get_shader_type())) { continue; } - - String name; - if (ref->has_method("_get_name")) { - name = (String)ref->call("_get_name"); - } else { - name = "Unnamed"; - } - - String description = ""; - if (ref->has_method("_get_description")) { - description = (String)ref->call("_get_description"); - } - - int return_icon_type = -1; - if (ref->has_method("_get_return_icon_type")) { - return_icon_type = (int)ref->call("_get_return_icon_type"); - } - - String category = ""; - if (ref->has_method("_get_category")) { - category = (String)ref->call("_get_category"); - } - - String subcategory = ""; - if (ref->has_method("_get_subcategory")) { - subcategory = (String)ref->call("_get_subcategory"); - } - - bool highend = false; - if (ref->has_method("_is_highend")) { - highend = (bool)ref->call("_is_highend"); - } - - Dictionary dict; - dict["name"] = name; - dict["script"] = scr; - dict["description"] = description; - dict["return_icon_type"] = return_icon_type; - - category = category.rstrip("/"); - category = category.lstrip("/"); - category = "Addons/" + category; - if (!subcategory.is_empty()) { - category += "/" + subcategory; - } - - dict["category"] = category; - dict["highend"] = highend; + Dictionary dict = get_custom_node_data(ref); String key; - key = category + "/" + name; + key = String(dict["category"]) + "/" + String(dict["name"]); added[key] = dict; } @@ -4694,6 +4878,8 @@ void VisualShaderEditor::_bind_methods() { ClassDB::bind_method("_update_constant", &VisualShaderEditor::_update_constant); ClassDB::bind_method("_update_parameter", &VisualShaderEditor::_update_parameter); ClassDB::bind_method("_expand_output_port", &VisualShaderEditor::_expand_output_port); + ClassDB::bind_method("_update_options_menu_deferred", &VisualShaderEditor::_update_options_menu_deferred); + ClassDB::bind_method("_rebuild_shader_deferred", &VisualShaderEditor::_rebuild_shader_deferred); ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &VisualShaderEditor::get_drag_data_fw); ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &VisualShaderEditor::can_drop_data_fw); @@ -4704,6 +4890,7 @@ void VisualShaderEditor::_bind_methods() { VisualShaderEditor::VisualShaderEditor() { ShaderLanguage::get_keyword_list(&keyword_list); + EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &VisualShaderEditor::update_custom_type)); graph = memnew(GraphEdit); graph->get_zoom_hbox()->set_h_size_flags(SIZE_EXPAND_FILL); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 8afad9f668..d559237569 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -133,6 +133,7 @@ public: void update_curve_xyz(int p_node_id); void set_expression(VisualShader::Type p_type, int p_node_id, const String &p_expression); int get_constant_index(float p_constant) const; + Ref<Script> get_node_script(int p_node_id) const; void update_node_size(int p_node_id); void update_theme(); VisualShader::Type get_shader_type() const; @@ -190,6 +191,9 @@ class VisualShaderEditor : public VBoxContainer { PanelContainer *error_panel = nullptr; Label *error_label = nullptr; + bool _block_update_options_menu = false; + bool _block_rebuild_shader = false; + Point2 saved_node_pos; bool saved_node_pos_dirty = false; @@ -497,6 +501,9 @@ class VisualShaderEditor : public VBoxContainer { void _update_parameter_refs(HashSet<String> &p_names); void _update_varyings(); + void _update_options_menu_deferred(); + void _rebuild_shader_deferred(); + void _visibility_changed(); protected: @@ -504,7 +511,6 @@ protected: static void _bind_methods(); public: - void update_nodes(); void add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); void remove_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); @@ -513,6 +519,9 @@ public: void clear_custom_types(); void add_custom_type(const String &p_name, const Ref<Script> &p_script, const String &p_description, int p_return_icon_type, const String &p_category, bool p_highend); + Dictionary get_custom_node_data(Ref<VisualShaderNodeCustom> &p_custom_node); + void update_custom_type(const Ref<Resource> &p_resource); + virtual Size2 get_minimum_size() const override; void edit(VisualShader *p_visual_shader); VisualShaderEditor(); diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 28111bed58..1e917e6b3d 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -274,10 +274,12 @@ void ProjectSettingsEditor::_add_feature_overrides() { presets.insert("s3tc"); presets.insert("etc"); presets.insert("etc2"); + presets.insert("editor"); + presets.insert("template_debug"); + presets.insert("template_release"); presets.insert("debug"); presets.insert("release"); - presets.insert("editor"); - presets.insert("standalone"); + presets.insert("template"); presets.insert("32"); presets.insert("64"); presets.insert("movie"); diff --git a/main/main.cpp b/main/main.cpp index c2e2238482..09cec68ef9 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1862,6 +1862,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF_BASIC("xr/openxr/reference_space", "1"); ProjectSettings::get_singleton()->set_custom_property_info("xr/openxr/reference_space", PropertyInfo(Variant::INT, "xr/openxr/reference_space", PROPERTY_HINT_ENUM, "Local,Stage")); + GLOBAL_DEF_BASIC("xr/openxr/submit_depth_buffer", false); + #ifdef TOOLS_ENABLED // Disabled for now, using XR inside of the editor we'll be working on during the coming months. @@ -2233,13 +2235,7 @@ Error Main::setup2(Thread::ID p_main_tid_override) { if (bool(GLOBAL_DEF("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) { - bool found_touchscreen = false; - for (int i = 0; i < DisplayServer::get_singleton()->get_screen_count(); i++) { - if (DisplayServer::get_singleton()->screen_is_touchscreen(i)) { - found_touchscreen = true; - } - } - if (!found_touchscreen) { + if (!DisplayServer::get_singleton()->is_touchscreen_available()) { //only if no touchscreen ui hint, set emulation id->set_emulate_touch_from_mouse(true); } @@ -3332,6 +3328,8 @@ void Main::cleanup(bool p_force) { ResourceLoader::clear_translation_remaps(); ResourceLoader::clear_path_remaps(); + ResourceLoader::clear_thread_load_tasks(); + ScriptServer::finish_languages(); // Sync pending commands that may have been queued from a different thread during ScriptServer finalization @@ -3365,6 +3363,7 @@ void Main::cleanup(bool p_force) { finalize_theme_db(); // Before deinitializing server extensions, finalize servers which may be loaded as extensions. + finalize_navigation_server(); finalize_physics(); NativeExtensionManager::get_singleton()->deinitialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SERVERS); @@ -3388,7 +3387,6 @@ void Main::cleanup(bool p_force) { OS::get_singleton()->finalize(); - finalize_navigation_server(); finalize_display(); if (input) { diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 60230257e0..c8195de640 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -2019,6 +2019,42 @@ Error GDScriptLanguage::execute_file(const String &p_path) { } void GDScriptLanguage::finish() { + if (_call_stack) { + memdelete_arr(_call_stack); + _call_stack = nullptr; + } + + // Clear the cache before parsing the script_list + GDScriptCache::clear(); + + // Clear dependencies between scripts, to ensure cyclic references are broken + // (to avoid leaks at exit). + SelfList<GDScript> *s = script_list.first(); + while (s) { + // This ensures the current script is not released before we can check + // what's the next one in the list (we can't get the next upfront because we + // don't know if the reference breaking will cause it -or any other after + // it, for that matter- to be released so the next one is not the same as + // before). + Ref<GDScript> scr = s->self(); + if (scr.is_valid()) { + for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { + GDScriptFunction *func = E.value; + for (int i = 0; i < func->argument_types.size(); i++) { + func->argument_types.write[i].script_type_ref = Ref<Script>(); + } + func->return_type.script_type_ref = Ref<Script>(); + } + for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { + E.value.data_type.script_type_ref = Ref<Script>(); + } + + // Clear backup for scripts that could slip out of the cyclic reference + // check + scr->clear(); + } + s = s->next(); + } } void GDScriptLanguage::profiling_start() { @@ -2530,36 +2566,6 @@ GDScriptLanguage::GDScriptLanguage() { } GDScriptLanguage::~GDScriptLanguage() { - if (_call_stack) { - memdelete_arr(_call_stack); - } - - // Clear dependencies between scripts, to ensure cyclic references are broken (to avoid leaks at exit). - SelfList<GDScript> *s = script_list.first(); - while (s) { - // This ensures the current script is not released before we can check what's the next one - // in the list (we can't get the next upfront because we don't know if the reference breaking - // will cause it -or any other after it, for that matter- to be released so the next one - // is not the same as before). - Ref<GDScript> scr = s->self(); - if (scr.is_valid()) { - for (KeyValue<StringName, GDScriptFunction *> &E : scr->member_functions) { - GDScriptFunction *func = E.value; - for (int i = 0; i < func->argument_types.size(); i++) { - func->argument_types.write[i].script_type_ref = Ref<Script>(); - } - func->return_type.script_type_ref = Ref<Script>(); - } - for (KeyValue<StringName, GDScript::MemberInfo> &E : scr->member_indices) { - E.value.data_type.script_type_ref = Ref<Script>(); - } - - // Clear backup for scripts that could slip out of the cyclic reference check - scr->clear(); - } - s = s->next(); - } - singleton = nullptr; } diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index 021504f242..1df7757082 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -381,15 +381,15 @@ void GDScriptCache::clear_unreferenced_packed_scenes() { } } -GDScriptCache::GDScriptCache() { - singleton = this; -} +void GDScriptCache::clear() { + if (singleton == nullptr) { + return; + } -GDScriptCache::~GDScriptCache() { - destructing = true; + MutexLock lock(singleton->mutex); RBSet<Ref<GDScriptParserRef>> parser_map_refs; - for (KeyValue<String, GDScriptParserRef *> &E : parser_map) { + for (KeyValue<String, GDScriptParserRef *> &E : singleton->parser_map) { parser_map_refs.insert(E.value); } @@ -398,13 +398,26 @@ GDScriptCache::~GDScriptCache() { E->clear(); } + for (KeyValue<String, HashSet<String>> &E : singleton->packed_scene_dependencies) { + singleton->packed_scene_dependencies.erase(E.key); + singleton->packed_scene_cache.erase(E.key); + } + parser_map_refs.clear(); - parser_map.clear(); - shallow_gdscript_cache.clear(); - full_gdscript_cache.clear(); + singleton->parser_map.clear(); + singleton->shallow_gdscript_cache.clear(); + singleton->full_gdscript_cache.clear(); - packed_scene_cache.clear(); - packed_scene_dependencies.clear(); + singleton->packed_scene_cache.clear(); + singleton->packed_scene_dependencies.clear(); +} +GDScriptCache::GDScriptCache() { + singleton = this; +} + +GDScriptCache::~GDScriptCache() { + destructing = true; + clear(); singleton = nullptr; } diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index 2195932aa3..e7e1901d5d 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -111,6 +111,8 @@ public: return singleton->destructing; }; + static void clear(); + GDScriptCache(); ~GDScriptCache(); }; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index a9b5ad107c..d3260d90a6 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -43,7 +43,7 @@ bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringN return false; } - if (codegen.parameters.has(p_name) || codegen.locals.has(p_name)) { + if (_is_local_or_parameter(codegen, p_name)) { return false; //shadowed } @@ -65,6 +65,10 @@ bool GDScriptCompiler::_is_class_member_property(GDScript *owner, const StringNa return ClassDB::has_property(nc->get_name(), p_name); } +bool GDScriptCompiler::_is_local_or_parameter(CodeGen &codegen, const StringName &p_name) { + return codegen.parameters.has(p_name) || codegen.locals.has(p_name); +} + void GDScriptCompiler::_set_error(const String &p_error, const GDScriptParser::Node *p_node) { if (!error.is_empty()) { return; @@ -920,7 +924,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code StringName var_name = identifier->name; if (_is_class_member_property(codegen, var_name)) { assign_class_member_property = var_name; - } else if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + } else if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member_property = true; member_property_setter_function = codegen.script->member_indices[var_name].setter; member_property_has_setter = member_property_setter_function != StringName(); @@ -1131,7 +1135,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool is_in_setter = false; StringName setter_function; StringName var_name = static_cast<const GDScriptParser::IdentifierNode *>(assignment->assignee)->name; - if (!codegen.locals.has(var_name) && codegen.script->member_indices.has(var_name)) { + if (!_is_local_or_parameter(codegen, var_name) && codegen.script->member_indices.has(var_name)) { is_member = true; setter_function = codegen.script->member_indices[var_name].setter; has_setter = setter_function != StringName(); diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 45ca4fe342..cba585e5a5 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -115,6 +115,7 @@ class GDScriptCompiler { bool _is_class_member_property(CodeGen &codegen, const StringName &p_name); bool _is_class_member_property(GDScript *owner, const StringName &p_name); + bool _is_local_or_parameter(CodeGen &codegen, const StringName &p_name); void _set_error(const String &p_error, const GDScriptParser::Node *p_node); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index c02ee99a86..693863ab38 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1612,7 +1612,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } } - if (!found) { + if (!found && base.value.get_type() != Variant::NIL) { found = _guess_method_return_type_from_base(c, base, call->function_name, r_type); } } @@ -2512,50 +2512,62 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } static bool _get_subscript_type(GDScriptParser::CompletionContext &p_context, const GDScriptParser::SubscriptNode *p_subscript, GDScriptParser::DataType &r_base_type, Variant *r_base = nullptr) { - if (p_subscript->base->type == GDScriptParser::Node::IDENTIFIER && p_context.base != nullptr) { - const GDScriptParser::GetNodeNode *get_node = nullptr; - const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); + if (p_context.base == nullptr) { + return false; + } + const GDScriptParser::GetNodeNode *get_node = nullptr; - switch (identifier_node->source) { - case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { - if (p_context.current_class != nullptr) { - const StringName &member_name = identifier_node->name; - const GDScriptParser::ClassNode *current_class = p_context.current_class; + switch (p_subscript->base->type) { + case GDScriptParser::Node::GET_NODE: { + get_node = static_cast<GDScriptParser::GetNodeNode *>(p_subscript->base); + } break; - if (current_class->has_member(member_name)) { - const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); + case GDScriptParser::Node::IDENTIFIER: { + const GDScriptParser::IdentifierNode *identifier_node = static_cast<GDScriptParser::IdentifierNode *>(p_subscript->base); - if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { - const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + switch (identifier_node->source) { + case GDScriptParser::IdentifierNode::Source::MEMBER_VARIABLE: { + if (p_context.current_class != nullptr) { + const StringName &member_name = identifier_node->name; + const GDScriptParser::ClassNode *current_class = p_context.current_class; - if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { - get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + if (current_class->has_member(member_name)) { + const GDScriptParser::ClassNode::Member &member = current_class->get_member(member_name); + + if (member.type == GDScriptParser::ClassNode::Member::VARIABLE) { + const GDScriptParser::VariableNode *variable = static_cast<GDScriptParser::VariableNode *>(member.variable); + + if (variable->initializer && variable->initializer->type == GDScriptParser::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(variable->initializer); + } } } } - } - } break; - case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { - if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { - get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); - } - } break; - default: - break; - } + } break; + case GDScriptParser::IdentifierNode::Source::LOCAL_VARIABLE: { + if (identifier_node->next != nullptr && identifier_node->next->type == GDScriptParser::ClassNode::Node::GET_NODE) { + get_node = static_cast<GDScriptParser::GetNodeNode *>(identifier_node->next); + } + } break; + default: { + } break; + } + } break; + default: { + } break; + } - if (get_node != nullptr) { - const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); - if (node != nullptr) { - if (r_base != nullptr) { - *r_base = node; - } - r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; - r_base_type.kind = GDScriptParser::DataType::NATIVE; - r_base_type.native_type = node->get_class_name(); - r_base_type.builtin_type = Variant::OBJECT; - return true; + if (get_node != nullptr) { + const Object *node = p_context.base->call("get_node_or_null", NodePath(get_node->full_path)); + if (node != nullptr) { + if (r_base != nullptr) { + *r_base = node; } + r_base_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + r_base_type.kind = GDScriptParser::DataType::NATIVE; + r_base_type.native_type = node->get_class_name(); + r_base_type.builtin_type = Variant::OBJECT; + return true; } } @@ -2612,7 +2624,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c } } - if (p_context.base != nullptr && subscript->is_attribute) { + if (subscript->is_attribute) { bool found_type = _get_subscript_type(p_context, subscript, base_type, &base); if (!found_type) { @@ -3276,6 +3288,7 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co parser.parse(p_code, p_path, true); GDScriptParser::CompletionContext context = parser.get_completion_context(); + context.base = p_owner; // Allows class functions with the names like built-ins to be handled properly. if (context.type != GDScriptParser::COMPLETION_ATTRIBUTE) { @@ -3448,7 +3461,9 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co break; } GDScriptCompletionIdentifier base; - if (!_guess_expression_type(context, subscript->base, base)) { + + bool found_type = _get_subscript_type(context, subscript, base.type); + if (!found_type && !_guess_expression_type(context, subscript->base, base)) { break; } diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp index de3becbaf8..e442bf8159 100644 --- a/modules/gdscript/language_server/gdscript_extend_parser.cpp +++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp @@ -844,8 +844,9 @@ Error ExtendGDScriptParser::parse(const String &p_code, const String &p_path) { lines = p_code.split("\n"); Error err = GDScriptParser::parse(p_code, p_path, false); + GDScriptAnalyzer analyzer(this); + if (err == OK) { - GDScriptAnalyzer analyzer(this); err = analyzer.analyze(); } update_diagnostics(); diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd new file mode 100644 index 0000000000..f33ba7dffd --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.gd @@ -0,0 +1,25 @@ +# https://github.com/godotengine/godot/pull/69620 + +var a: int = 1 + +func shadow_regular_assignment(a: Variant, b: Variant) -> void: + print(a) + print(self.a) + a = b + print(a) + print(self.a) + + +var v := Vector2(0.0, 0.0) + +func shadow_subscript_assignment(v: Vector2, x: float) -> void: + print(v) + print(self.v) + v.x += x + print(v) + print(self.v) + + +func test(): + shadow_regular_assignment('a', 'b') + shadow_subscript_assignment(Vector2(1.0, 1.0), 5.0) diff --git a/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out new file mode 100644 index 0000000000..5b981bc8bb --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/parameter_shadowing.out @@ -0,0 +1,17 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> SHADOWED_VARIABLE +>> The local function parameter "a" is shadowing an already-declared variable at line 3. +>> WARNING +>> Line: 15 +>> SHADOWED_VARIABLE +>> The local function parameter "v" is shadowing an already-declared variable at line 13. +a +1 +b +1 +(1, 1) +(0, 0) +(6, 1) +(0, 0) diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp index 3cf49a3046..a194719b91 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.cpp +++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp @@ -63,7 +63,12 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t if (p_options.has("animation/import")) { state->set_create_animations(bool(p_options["animation/import"])); } - return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); + + if (p_options.has("animation/trimming")) { + return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]); + } else { + return doc->generate_scene(state, (float)p_options["animation/fps"], false); + } } #endif // TOOLS_ENABLED diff --git a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml index 7ed6255a62..af7c345f15 100644 --- a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml +++ b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml @@ -17,7 +17,7 @@ <param index="0" name="filter" type="Callable" /> <description> Adds a peer visibility filter for this synchronizer. - [code]filter[/code] should take a peer id [int] and return a [bool]. + [code]filter[/code] should take a peer ID [int] and return a [bool]. </description> </method> <method name="get_visibility_for" qualifiers="const"> diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index 59d3865acd..b652ca4617 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -740,9 +740,10 @@ bool OpenXRAPI::create_swapchains() { ERR_FAIL_NULL_V_MSG(projection_views, false, "OpenXR Couldn't allocate memory for projection views"); // We create our depth swapchain if: + // - we've enabled submitting depth buffer // - we support our depth layer extension // - we have our spacewarp extension (not yet implemented) - if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { + if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available()) { // Build a vector with swapchain formats we want to use, from best fit to worst Vector<int64_t> usable_swapchain_formats; int64_t swapchain_format_to_use = 0; @@ -790,13 +791,13 @@ bool OpenXRAPI::create_swapchains() { projection_views[i].subImage.imageRect.extent.width = recommended_size.width; projection_views[i].subImage.imageRect.extent.height = recommended_size.height; - if (OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { + if (submit_depth_buffer && OpenXRCompositionLayerDepthExtension::get_singleton()->is_available() && depth_views) { projection_views[i].next = &depth_views[i]; depth_views[i].type = XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR; depth_views[i].next = nullptr; depth_views[i].subImage.swapchain = swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain; - depth_views[i].subImage.imageArrayIndex = 0; + depth_views[i].subImage.imageArrayIndex = i; depth_views[i].subImage.imageRect.offset.x = 0; depth_views[i].subImage.imageRect.offset.y = 0; depth_views[i].subImage.imageRect.extent.width = recommended_size.width; @@ -1066,6 +1067,30 @@ bool OpenXRAPI::on_state_exiting() { return true; } +void OpenXRAPI::set_form_factor(XrFormFactor p_form_factor) { + ERR_FAIL_COND(is_initialized()); + + form_factor = p_form_factor; +} + +void OpenXRAPI::set_view_configuration(XrViewConfigurationType p_view_configuration) { + ERR_FAIL_COND(is_initialized()); + + view_configuration = p_view_configuration; +} + +void OpenXRAPI::set_reference_space(XrReferenceSpaceType p_reference_space) { + ERR_FAIL_COND(is_initialized()); + + reference_space = p_reference_space; +} + +void OpenXRAPI::set_submit_depth_buffer(bool p_submit_depth_buffer) { + ERR_FAIL_COND(is_initialized()); + + submit_depth_buffer = p_submit_depth_buffer; +} + bool OpenXRAPI::is_initialized() { return (instance != XR_NULL_HANDLE); } @@ -1684,7 +1709,7 @@ RID OpenXRAPI::get_color_texture() { } RID OpenXRAPI::get_depth_texture() { - if (swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) { + if (submit_depth_buffer && swapchains[OPENXR_SWAPCHAIN_DEPTH].image_acquired) { return graphics_extension->get_texture(swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data, swapchains[OPENXR_SWAPCHAIN_DEPTH].image_index); } else { return RID(); @@ -1862,6 +1887,8 @@ OpenXRAPI::OpenXRAPI() { default: break; } + + submit_depth_buffer = GLOBAL_GET("xr/openxr/submit_depth_buffer"); } // reset a few things that can't be done in our class definition diff --git a/modules/openxr/openxr_api.h b/modules/openxr/openxr_api.h index 5dce749351..ac4bbff94c 100644 --- a/modules/openxr/openxr_api.h +++ b/modules/openxr/openxr_api.h @@ -104,6 +104,7 @@ private: XrViewConfigurationType view_configuration = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; XrReferenceSpaceType reference_space = XR_REFERENCE_SPACE_TYPE_STAGE; // XrEnvironmentBlendMode environment_blend_mode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + bool submit_depth_buffer = false; // if set to true we submit depth buffers to OpenXR if a suitable extension is enabled. // state XrInstance instance = XR_NULL_HANDLE; @@ -312,6 +313,18 @@ public: void set_xr_interface(OpenXRInterface *p_xr_interface); void register_extension_wrapper(OpenXRExtensionWrapper *p_extension_wrapper); + void set_form_factor(XrFormFactor p_form_factor); + XrFormFactor get_form_factor() const { return form_factor; } + + void set_view_configuration(XrViewConfigurationType p_view_configuration); + XrViewConfigurationType get_view_configuration() const { return view_configuration; } + + void set_reference_space(XrReferenceSpaceType p_reference_space); + XrReferenceSpaceType get_reference_space() const { return reference_space; } + + void set_submit_depth_buffer(bool p_submit_depth_buffer); + bool get_submit_depth_buffer() const { return submit_depth_buffer; } + bool is_initialized(); bool is_running(); bool initialize(const String &p_rendering_driver); diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp index 77660eb6f0..40190ab2f3 100644 --- a/modules/openxr/openxr_interface.cpp +++ b/modules/openxr/openxr_interface.cpp @@ -45,7 +45,7 @@ void OpenXRInterface::_bind_methods() { // Display refresh rate ClassDB::bind_method(D_METHOD("get_display_refresh_rate"), &OpenXRInterface::get_display_refresh_rate); ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &OpenXRInterface::set_display_refresh_rate); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "display_refresh_rate"), "set_display_refresh_rate", "get_display_refresh_rate"); ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &OpenXRInterface::get_available_display_refresh_rates); } diff --git a/modules/webrtc/doc_classes/WebRTCDataChannel.xml b/modules/webrtc/doc_classes/WebRTCDataChannel.xml index a9ba8a23de..a186631ca8 100644 --- a/modules/webrtc/doc_classes/WebRTCDataChannel.xml +++ b/modules/webrtc/doc_classes/WebRTCDataChannel.xml @@ -22,8 +22,8 @@ <method name="get_id" qualifiers="const"> <return type="int" /> <description> - Returns the id assigned to this channel during creation (or auto-assigned during negotiation). - If the channel is not negotiated out-of-band the id will only be available after the connection is established (will return [code]65535[/code] until then). + Returns the ID assigned to this channel during creation (or auto-assigned during negotiation). + If the channel is not negotiated out-of-band the ID will only be available after the connection is established (will return [code]65535[/code] until then). </description> </method> <method name="get_label" qualifiers="const"> diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 937b929d62..9c796a8c07 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -221,7 +221,7 @@ float DisplayServerAndroid::screen_get_refresh_rate(int p_screen) const { return godot_io_java->get_screen_refresh_rate(SCREEN_REFRESH_RATE_FALLBACK); } -bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const { +bool DisplayServerAndroid::is_touchscreen_available() const { return true; } @@ -366,6 +366,10 @@ Point2i DisplayServerAndroid::window_get_position(DisplayServer::WindowID p_wind return Point2i(); } +Point2i DisplayServerAndroid::window_get_position_with_decorations(DisplayServer::WindowID p_window) const { + return Point2i(); +} + void DisplayServerAndroid::window_set_position(const Point2i &p_position, DisplayServer::WindowID p_window) { // Not supported on Android. } @@ -398,7 +402,7 @@ Size2i DisplayServerAndroid::window_get_size(DisplayServer::WindowID p_window) c return OS_Android::get_singleton()->get_display_size(); } -Size2i DisplayServerAndroid::window_get_real_size(DisplayServer::WindowID p_window) const { +Size2i DisplayServerAndroid::window_get_size_with_decorations(DisplayServer::WindowID p_window) const { return OS_Android::get_singleton()->get_display_size(); } diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h index c7f4d8046f..9ed07ca971 100644 --- a/platform/android/display_server_android.h +++ b/platform/android/display_server_android.h @@ -121,7 +121,7 @@ public: virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; - virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual bool is_touchscreen_available() const override; virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), VirtualKeyboardType p_type = KEYBOARD_TYPE_DEFAULT, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override; virtual void virtual_keyboard_hide() override; @@ -150,6 +150,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; @@ -162,7 +163,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h index 447f919139..4901eeefaf 100644 --- a/platform/ios/display_server_ios.h +++ b/platform/ios/display_server_ios.h @@ -162,6 +162,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; @@ -174,7 +175,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; @@ -199,7 +200,7 @@ public: virtual void window_set_vsync_mode(DisplayServer::VSyncMode p_vsync_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual DisplayServer::VSyncMode window_get_vsync_mode(WindowID p_vsync_mode) const override; - virtual bool screen_is_touchscreen(int p_screen) const override; + virtual bool is_touchscreen_available() const override; virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, VirtualKeyboardType p_type, int p_max_length, int p_cursor_start, int p_cursor_end) override; virtual void virtual_keyboard_hide() override; diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm index 6793b40dd4..ed1bc499ed 100644 --- a/platform/ios/display_server_ios.mm +++ b/platform/ios/display_server_ios.mm @@ -496,6 +496,10 @@ Point2i DisplayServerIOS::window_get_position(WindowID p_window) const { return Point2i(); } +Point2i DisplayServerIOS::window_get_position_with_decorations(WindowID p_window) const { + return Point2i(); +} + void DisplayServerIOS::window_set_position(const Point2i &p_position, WindowID p_window) { // Probably not supported for single window iOS app } @@ -529,7 +533,7 @@ Size2i DisplayServerIOS::window_get_size(WindowID p_window) const { return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale(); } -Size2i DisplayServerIOS::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerIOS::window_get_size_with_decorations(WindowID p_window) const { return window_get_size(p_window); } @@ -581,7 +585,7 @@ bool DisplayServerIOS::can_any_window_draw() const { return true; } -bool DisplayServerIOS::screen_is_touchscreen(int p_screen) const { +bool DisplayServerIOS::is_touchscreen_available() const { return true; } diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index a5e3e60775..1c5c8b2d19 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -1397,8 +1397,8 @@ void DisplayServerX11::window_set_mouse_passthrough(const Vector<Vector2> &p_reg XRectangle rect; rect.x = 0; rect.y = 0; - rect.width = window_get_real_size(p_window).x; - rect.height = window_get_real_size(p_window).y; + rect.width = window_get_size_with_decorations(p_window).x; + rect.height = window_get_size_with_decorations(p_window).y; XUnionRectWithRegion(&rect, region, region); } else { XPoint *points = (XPoint *)memalloc(sizeof(XPoint) * p_region.size()); @@ -1582,7 +1582,7 @@ void DisplayServerX11::_update_size_hints(WindowID p_window) { xsh->width = wd.size.width; xsh->height = wd.size.height; - if (window_mode == WINDOW_MODE_FULLSCREEN) { + if (window_mode == WINDOW_MODE_FULLSCREEN || window_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { // Do not set any other hints to prevent the window manager from ignoring the fullscreen flags } else if (window_get_flag(WINDOW_FLAG_RESIZE_DISABLED, p_window)) { // If resizing is disabled, use the forced size @@ -1618,6 +1618,40 @@ Point2i DisplayServerX11::window_get_position(WindowID p_window) const { return wd.position; } +Point2i DisplayServerX11::window_get_position_with_decorations(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); + const WindowData &wd = windows[p_window]; + + if (wd.fullscreen) { + return wd.position; + } + + XWindowAttributes xwa; + XSync(x11_display, False); + XGetWindowAttributes(x11_display, wd.x11_window, &xwa); + int x = wd.position.x; + int y = wd.position.y; + Atom prop = XInternAtom(x11_display, "_NET_FRAME_EXTENTS", True); + if (prop != None) { + Atom type; + int format; + unsigned long len; + unsigned long remaining; + unsigned char *data = nullptr; + if (XGetWindowProperty(x11_display, wd.x11_window, prop, 0, 4, False, AnyPropertyType, &type, &format, &len, &remaining, &data) == Success) { + if (format == 32 && len == 4 && data) { + long *extents = (long *)data; + x -= extents[0]; // left + y -= extents[2]; // top + } + XFree(data); + } + } + return Size2i(x, y); +} + void DisplayServerX11::window_set_position(const Point2i &p_position, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -1762,12 +1796,16 @@ Size2i DisplayServerX11::window_get_size(WindowID p_window) const { return wd.size; } -Size2i DisplayServerX11::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerX11::window_get_size_with_decorations(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); const WindowData &wd = windows[p_window]; + if (wd.fullscreen) { + return wd.size; + } + XWindowAttributes xwa; XSync(x11_display, False); XGetWindowAttributes(x11_display, wd.x11_window, &xwa); @@ -1949,7 +1987,7 @@ void DisplayServerX11::_validate_mode_on_map(WindowID p_window) { // Check if we applied any window modes that didn't take effect while unmapped const WindowData &wd = windows[p_window]; if (wd.fullscreen && !_window_fullscreen_check(p_window)) { - _set_wm_fullscreen(p_window, true); + _set_wm_fullscreen(p_window, true, wd.exclusive_fullscreen); } else if (wd.maximized && !_window_maximize_check(p_window, "_NET_WM_STATE")) { _set_wm_maximized(p_window, true); } else if (wd.minimized && !_window_minimize_check(p_window)) { @@ -2024,7 +2062,7 @@ void DisplayServerX11::_set_wm_minimized(WindowID p_window, bool p_enabled) { wd.minimized = p_enabled; } -void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { +void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled, bool p_exclusive) { ERR_FAIL_COND(!windows.has(p_window)); WindowData &wd = windows[p_window]; @@ -2063,7 +2101,14 @@ void DisplayServerX11::_set_wm_fullscreen(WindowID p_window, bool p_enabled) { // set bypass compositor hint Atom bypass_compositor = XInternAtom(x11_display, "_NET_WM_BYPASS_COMPOSITOR", False); - unsigned long compositing_disable_on = p_enabled ? 1 : 0; + unsigned long compositing_disable_on = 0; // Use default. + if (p_enabled) { + if (p_exclusive) { + compositing_disable_on = 1; // Force composition OFF to reduce overhead. + } else { + compositing_disable_on = 2; // Force composition ON to allow popup windows. + } + } if (bypass_compositor != None) { XChangeProperty(x11_display, wd.x11_window, bypass_compositor, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&compositing_disable_on, 1); } @@ -2109,8 +2154,9 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { case WINDOW_MODE_FULLSCREEN: { //Remove full-screen wd.fullscreen = false; + wd.exclusive_fullscreen = false; - _set_wm_fullscreen(p_window, false); + _set_wm_fullscreen(p_window, false, false); //un-maximize required for always on top bool on_top = window_get_flag(WINDOW_FLAG_ALWAYS_ON_TOP, p_window); @@ -2143,7 +2189,13 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) { } wd.fullscreen = true; - _set_wm_fullscreen(p_window, true); + if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) { + wd.exclusive_fullscreen = true; + _set_wm_fullscreen(p_window, true, true); + } else { + wd.exclusive_fullscreen = false; + _set_wm_fullscreen(p_window, true, false); + } } break; case WINDOW_MODE_MAXIMIZED: { _set_wm_maximized(p_window, true); @@ -2158,7 +2210,11 @@ DisplayServer::WindowMode DisplayServerX11::window_get_mode(WindowID p_window) c const WindowData &wd = windows[p_window]; if (wd.fullscreen) { //if fullscreen, it's not in another mode - return WINDOW_MODE_FULLSCREEN; + if (wd.exclusive_fullscreen) { + return WINDOW_MODE_EXCLUSIVE_FULLSCREEN; + } else { + return WINDOW_MODE_FULLSCREEN; + } } // Test maximized. @@ -3947,7 +4003,6 @@ void DisplayServerX11::process_events() { mb->set_window_id(window_id_other); mb->set_position(Vector2(x, y)); mb->set_global_position(mb->get_position()); - Input::get_singleton()->parse_input_event(mb); } break; } diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h index c88a6b466a..2578ca06fd 100644 --- a/platform/linuxbsd/x11/display_server_x11.h +++ b/platform/linuxbsd/x11/display_server_x11.h @@ -159,6 +159,7 @@ class DisplayServerX11 : public DisplayServer { //better to guess on the fly, given WM can change it //WindowMode mode; bool fullscreen = false; //OS can't exit from this mode + bool exclusive_fullscreen = false; bool on_top = false; bool borderless = false; bool resize_disabled = false; @@ -283,7 +284,7 @@ class DisplayServerX11 : public DisplayServer { bool _window_minimize_check(WindowID p_window) const; void _validate_mode_on_map(WindowID p_window); void _update_size_hints(WindowID p_window); - void _set_wm_fullscreen(WindowID p_window, bool p_enabled); + void _set_wm_fullscreen(WindowID p_window, bool p_enabled, bool p_exclusive); void _set_wm_maximized(WindowID p_window, bool p_enabled); void _set_wm_minimized(WindowID p_window, bool p_enabled); @@ -392,6 +393,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; @@ -405,7 +407,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 8f315f736b..bd26f6e417 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -355,6 +355,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; @@ -368,7 +369,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(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 ad6143e16e..5c979dbf22 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -2414,6 +2414,27 @@ Point2i DisplayServerMacOS::window_get_position(WindowID p_window) const { return pos; } +Point2i DisplayServerMacOS::window_get_position_with_decorations(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Point2i()); + const WindowData &wd = windows[p_window]; + + const NSRect nsrect = [wd.window_object frame]; + Point2i pos; + + // Return the position of the top-left corner, for OS X the y starts at the bottom. + const float scale = screen_get_max_scale(); + pos.x = nsrect.origin.x; + pos.y = (nsrect.origin.y + nsrect.size.height); + pos *= scale; + pos -= _get_screens_origin(); + // OS X native y-coordinate relative to _get_screens_origin() is negative, + // Godot expects a positive value. + pos.y *= -1; + return pos; +} + void DisplayServerMacOS::window_set_position(const Point2i &p_position, WindowID p_window) { _THREAD_SAFE_METHOD_ @@ -2573,7 +2594,7 @@ Size2i DisplayServerMacOS::window_get_size(WindowID p_window) const { return wd.size; } -Size2i DisplayServerMacOS::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerMacOS::window_get_size_with_decorations(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 5e71d10a3f..49c8c7758d 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -91,11 +91,14 @@ bool EditorExportPlatformMacOS::get_export_option_visibility(const EditorExportP return false; } } break; - case 2: { // "altool" + case 2: { // "notarytool" + // All options are visible. + } break; + case 3: { // "altool" // All options are visible. } break; default: { // disabled - if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key") { + if (p_option == "notarization/apple_id_name" || p_option == "notarization/apple_id_password" || p_option == "notarization/apple_team_id" || p_option == "notarization/api_uuid" || p_option == "notarization/api_key" || p_option == "notarization/api_key_id") { return false; } } break; @@ -129,9 +132,9 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), false)); #ifdef MACOS_ENABLED - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign,Xcode codesign"), 3, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),rcodesign,Xcode codesign"), 3, true)); #else - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),PyOxidizer rcodesign"), 1, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "codesign/codesign", PROPERTY_HINT_ENUM, "Disabled,Built-in (ad-hoc only),rcodesign"), 1, true)); #endif // "codesign" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "codesign/identity", PROPERTY_HINT_PLACEHOLDER_TEXT, "Type: Name (ID)"), "")); @@ -165,17 +168,18 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray())); #ifdef MACOS_ENABLED - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign,Xcode altool"), 0, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,rcodesign,Xcode notarytool,Xcode altool (deprecated)"), 0, true)); #else - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,PyOxidizer rcodesign"), 0, true)); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "notarization/notarization", PROPERTY_HINT_ENUM, "Disabled,rcodesign"), 0, true)); #endif - // "altool" only options: + // "altool" and "notarytool" only options: r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Apple ID email"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_id_password", PROPERTY_HINT_PASSWORD, "Enable two-factor authentication and provide app-specific password"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/apple_team_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide team ID if your Apple ID belongs to multiple teams"), "")); - // "altool" and "rcodesign" only options: - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID"), "")); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), "")); + // "altool", "notarytool" and "rcodesign" only options: + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_uuid", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect issuer ID UUID"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key", PROPERTY_HINT_GLOBAL_FILE, "*.p8"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "notarization/api_key_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "App Store Connect API key ID"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "privacy/microphone_usage_description", PROPERTY_HINT_PLACEHOLDER_TEXT, "Provide a message if you need to use the microphone"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "privacy/microphone_usage_description_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); @@ -498,7 +502,12 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres args.push_back(p_preset->get("notarization/api_uuid")); args.push_back("--api-key"); - args.push_back(p_preset->get("notarization/api_key")); + args.push_back(p_preset->get("notarization/api_key_id")); + + if (!p_preset->get("notarization/api_key").operator String().is_empty()) { + args.push_back("--api-key-path"); + args.push_back(p_preset->get("notarization/api_key")); + } args.push_back(p_path); @@ -519,7 +528,7 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres } else { print_verbose("rcodesign (" + p_path + "):\n" + str); int next_nl = str.find("\n", rq_offset); - String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14); + String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 23, -1) : str.substr(rq_offset + 23, next_nl - rq_offset - 23); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour.")); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); @@ -529,7 +538,91 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres } } break; #ifdef MACOS_ENABLED - case 2: { // "altool" + case 2: { // "notarytool" + print_verbose("using notarytool notarization..."); + + if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Xcode command line tools are not installed.")); + return Error::FAILED; + } + + List<String> args; + + args.push_back("notarytool"); + args.push_back("submit"); + + args.push_back(p_path); + + if (p_preset->get("notarization/apple_id_name") == "" && p_preset->get("notarization/api_uuid") == "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Neither Apple ID name nor App Store Connect issuer ID name not specified.")); + return Error::FAILED; + } + if (p_preset->get("notarization/apple_id_name") != "" && p_preset->get("notarization/api_uuid") != "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Both Apple ID name and App Store Connect issuer ID name are specified, only one should be set at the same time.")); + return Error::FAILED; + } + + if (p_preset->get("notarization/apple_id_name") != "") { + if (p_preset->get("notarization/apple_id_password") == "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("Apple ID password not specified.")); + return Error::FAILED; + } + args.push_back("--apple-id"); + args.push_back(p_preset->get("notarization/apple_id_name")); + + args.push_back("--password"); + args.push_back(p_preset->get("notarization/apple_id_password")); + } else { + if (p_preset->get("notarization/api_key_id") == "") { + add_message(EXPORT_MESSAGE_ERROR, TTR("Notarization"), TTR("App Store Connect API key ID not specified.")); + return Error::FAILED; + } + args.push_back("--issuer"); + args.push_back(p_preset->get("notarization/api_uuid")); + + if (!p_preset->get("notarization/api_key").operator String().is_empty()) { + args.push_back("--key"); + args.push_back(p_preset->get("notarization/api_key")); + } + + args.push_back("--key-id"); + args.push_back(p_preset->get("notarization/api_key_id")); + } + + args.push_back("--no-progress"); + + if (p_preset->get("notarization/apple_team_id")) { + args.push_back("--team-id"); + args.push_back(p_preset->get("notarization/apple_team_id")); + } + + String str; + int exitcode = 0; + Error err = OS::get_singleton()->execute("xcrun", args, &str, &exitcode, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable.")); + return err; + } + + int rq_offset = str.find("id:"); + if (exitcode != 0 || rq_offset == -1) { + print_line("notarytool (" + p_path + "):\n" + str); + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed, see editor log for details.")); + return Error::FAILED; + } else { + print_verbose("notarytool (" + p_path + "):\n" + str); + int next_nl = str.find("\n", rq_offset); + String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 4, -1) : str.substr(rq_offset + 4, next_nl - rq_offset - 4); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour.")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun notarytool log <request uuid> --issuer <api uuid> --key-id <api key id> --key <api key path>\" or"); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun notarytool log <request uuid> --apple-id <your email> --password <app-specific pwd>>\""); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple <app path>\""); + } + } break; + case 3: { // "altool" print_verbose("using altool notarization..."); if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) { @@ -573,7 +666,7 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres args.push_back(p_preset->get("notarization/api_uuid")); args.push_back("--apiKey"); - args.push_back(p_preset->get("notarization/api_key")); + args.push_back(p_preset->get("notarization/api_key_id")); } args.push_back("--type"); @@ -595,7 +688,7 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres return err; } - int rq_offset = str.find("RequestUUID"); + int rq_offset = str.find("RequestUUID:"); if (exitcode != 0 || rq_offset == -1) { print_line("xcrun altool (" + p_path + "):\n" + str); add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed, see editor log for details.")); @@ -603,7 +696,7 @@ Error EditorExportPlatformMacOS::_notarize(const Ref<EditorExportPreset> &p_pres } else { print_verbose("xcrun altool (" + p_path + "):\n" + str); int next_nl = str.find("\n", rq_offset); - String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14); + String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 13, -1) : str.substr(rq_offset + 13, next_nl - rq_offset - 13); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.")); add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); @@ -1819,7 +1912,7 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor err += TTR("Notarization: Code signing is required for notarization.") + "\n"; valid = false; } - if (notary_tool == 2) { + if (notary_tool == 2 || notary_tool == 3) { if (!FileAccess::exists("/usr/bin/xcrun") && !FileAccess::exists("/bin/xcrun")) { err += TTR("Notarization: Xcode command line tools are not installed.") + "\n"; valid = false; @@ -1838,7 +1931,7 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor } } if (p_preset->get("notarization/api_uuid") != "") { - if (p_preset->get("notarization/api_key") == "") { + if (p_preset->get("notarization/api_key_id") == "") { err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n"; valid = false; } @@ -1849,7 +1942,7 @@ bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<Editor err += TTR("Notarization: App Store Connect issuer ID name not specified.") + "\n"; valid = false; } - if (p_preset->get("notarization/api_key") == "") { + if (p_preset->get("notarization/api_key_id") == "") { err += TTR("Notarization: App Store Connect API key ID not specified.") + "\n"; valid = false; } diff --git a/platform/web/display_server_web.cpp b/platform/web/display_server_web.cpp index f704124704..d057010c02 100644 --- a/platform/web/display_server_web.cpp +++ b/platform/web/display_server_web.cpp @@ -579,8 +579,8 @@ void DisplayServerWeb::touch_callback(int p_type, int p_count) { } } -bool DisplayServerWeb::screen_is_touchscreen(int p_screen) const { - return godot_js_display_touchscreen_is_available(); +bool DisplayServerWeb::is_touchscreen_available() const { + return godot_js_display_touchscreen_is_available() || (Input::get_singleton() && Input::get_singleton()->is_emulating_touch_from_mouse()); } // Virtual Keyboard @@ -943,7 +943,11 @@ void DisplayServerWeb::window_set_current_screen(int p_screen, WindowID p_window } Point2i DisplayServerWeb::window_get_position(WindowID p_window) const { - return Point2i(); // TODO Does this need implementation? + return Point2i(); +} + +Point2i DisplayServerWeb::window_get_position_with_decorations(WindowID p_window) const { + return Point2i(); } void DisplayServerWeb::window_set_position(const Point2i &p_position, WindowID p_window) { @@ -980,7 +984,7 @@ Size2i DisplayServerWeb::window_get_size(WindowID p_window) const { return Size2i(size[0], size[1]); } -Size2i DisplayServerWeb::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerWeb::window_get_size_with_decorations(WindowID p_window) const { return window_get_size(p_window); } diff --git a/platform/web/display_server_web.h b/platform/web/display_server_web.h index 1919736802..fce98ec3d0 100644 --- a/platform/web/display_server_web.h +++ b/platform/web/display_server_web.h @@ -143,7 +143,7 @@ public: virtual Point2i mouse_get_position() const override; // touch - virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual bool is_touchscreen_available() const override; // clipboard virtual void clipboard_set(const String &p_text) override; @@ -182,6 +182,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; @@ -194,7 +195,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index c704a26b7a..e7864ebac0 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -909,6 +909,24 @@ Point2i DisplayServerWindows::window_get_position(WindowID p_window) const { return Point2i(point.x, point.y); } +Point2i DisplayServerWindows::window_get_position_with_decorations(WindowID p_window) const { + _THREAD_SAFE_METHOD_ + + ERR_FAIL_COND_V(!windows.has(p_window), Point2i()); + const WindowData &wd = windows[p_window]; + + if (wd.minimized) { + return wd.last_pos; + } + + RECT r; + if (GetWindowRect(wd.hWnd, &r)) { + return Point2i(r.left, r.top); + } + + return Point2i(); +} + void DisplayServerWindows::_update_real_mouse_position(WindowID p_window) { ERR_FAIL_COND(!windows.has(p_window)); @@ -1124,7 +1142,7 @@ Size2i DisplayServerWindows::window_get_size(WindowID p_window) const { return Size2(); } -Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const { +Size2i DisplayServerWindows::window_get_size_with_decorations(WindowID p_window) const { _THREAD_SAFE_METHOD_ ERR_FAIL_COND_V(!windows.has(p_window), Size2i()); @@ -2414,7 +2432,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA case WM_GETMINMAXINFO: { if (windows[window_id].resizable && !windows[window_id].fullscreen) { // Size of window decorations. - Size2 decor = window_get_real_size(window_id) - window_get_size(window_id); + Size2 decor = window_get_size_with_decorations(window_id) - window_get_size(window_id); MINMAXINFO *min_max_info = (MINMAXINFO *)lParam; if (windows[window_id].min_size != Size2()) { @@ -2473,7 +2491,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA window_mouseover_id = INVALID_WINDOW_ID; _send_window_event(windows[window_id], WINDOW_EVENT_MOUSE_EXIT); - } else if (window_mouseover_id != INVALID_WINDOW_ID) { + } else if (window_mouseover_id != INVALID_WINDOW_ID && windows.has(window_mouseover_id)) { // This is reached during drag and drop, after dropping in a different window. // Once-off notification, must call again. track_mouse_leave_event(windows[window_mouseover_id].hWnd); @@ -2712,7 +2730,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Mouse enter. if (mouse_mode != MOUSE_MODE_CAPTURED) { - if (window_mouseover_id != INVALID_WINDOW_ID) { + if (window_mouseover_id != INVALID_WINDOW_ID && windows.has(window_mouseover_id)) { // Leave previous window. _send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT); } @@ -2814,7 +2832,7 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA } DisplayServer::WindowID over_id = get_window_at_screen_position(mouse_get_position()); - if (!Rect2(window_get_position(over_id), Point2(windows[over_id].width, windows[over_id].height)).has_point(mouse_get_position())) { + if (windows.has(over_id) && !Rect2(window_get_position(over_id), Point2(windows[over_id].width, windows[over_id].height)).has_point(mouse_get_position())) { // Don't consider the windowborder as part of the window. over_id = INVALID_WINDOW_ID; } @@ -2822,12 +2840,12 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA // Mouse enter. if (mouse_mode != MOUSE_MODE_CAPTURED) { - if (window_mouseover_id != INVALID_WINDOW_ID) { + if (window_mouseover_id != INVALID_WINDOW_ID && windows.has(window_mouseover_id)) { // Leave previous window. _send_window_event(windows[window_mouseover_id], WINDOW_EVENT_MOUSE_EXIT); } - if (over_id != INVALID_WINDOW_ID) { + if (over_id != INVALID_WINDOW_ID && windows.has(over_id)) { _send_window_event(windows[over_id], WINDOW_EVENT_MOUSE_ENTER); } } @@ -3520,7 +3538,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode, DWORD dwExStyle; DWORD dwStyle; - _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle); + _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) | (p_flags & WINDOW_FLAG_POPUP), dwStyle, dwExStyle); RECT WindowRect; diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 8ac0086d69..4702bb7765 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -555,6 +555,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override; virtual void window_set_transient(WindowID p_window, WindowID p_parent) override; @@ -568,7 +569,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override; //wtf is this? should probable use proper name + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override; diff --git a/scene/2d/back_buffer_copy.cpp b/scene/2d/back_buffer_copy.cpp index aa4ae01fd9..9c332123e3 100644 --- a/scene/2d/back_buffer_copy.cpp +++ b/scene/2d/back_buffer_copy.cpp @@ -71,12 +71,19 @@ Rect2 BackBufferCopy::get_rect() const { void BackBufferCopy::set_copy_mode(CopyMode p_mode) { copy_mode = p_mode; _update_copy_mode(); + notify_property_list_changed(); } BackBufferCopy::CopyMode BackBufferCopy::get_copy_mode() const { return copy_mode; } +void BackBufferCopy::_validate_property(PropertyInfo &p_property) const { + if (copy_mode != COPY_MODE_RECT && p_property.name == "rect") { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + void BackBufferCopy::_bind_methods() { ClassDB::bind_method(D_METHOD("set_rect", "rect"), &BackBufferCopy::set_rect); ClassDB::bind_method(D_METHOD("get_rect"), &BackBufferCopy::get_rect); diff --git a/scene/2d/back_buffer_copy.h b/scene/2d/back_buffer_copy.h index 1f2d5810b0..caacbc83c6 100644 --- a/scene/2d/back_buffer_copy.h +++ b/scene/2d/back_buffer_copy.h @@ -51,6 +51,7 @@ private: protected: static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; public: #ifdef TOOLS_ENABLED diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp index 4b31bbddac..229625ad6d 100644 --- a/scene/2d/camera_2d.cpp +++ b/scene/2d/camera_2d.cpp @@ -39,8 +39,11 @@ void Camera2D::_update_scroll() { } if (Engine::get_singleton()->is_editor_hint()) { - queue_redraw(); //will just be drawn - return; + queue_redraw(); + // Only set viewport transform when not bound to the main viewport. + if (get_viewport() == get_tree()->get_edited_scene_root()->get_viewport()) { + return; + } } if (!viewport) { diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index b68a8fb031..823b8d56e3 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -106,18 +106,57 @@ void Path2D::_notification(int p_what) { #else const real_t line_width = get_tree()->get_debug_paths_width(); #endif - _cached_draw_pts.resize(curve->get_point_count() * 8); - int count = 0; - - for (int i = 0; i < curve->get_point_count(); i++) { - for (int j = 0; j < 8; j++) { - real_t frac = j * (1.0 / 8.0); - Vector2 p = curve->sample(i, frac); - _cached_draw_pts.set(count++, p); + real_t interval = 10; + const real_t length = curve->get_baked_length(); + + if (length > CMP_EPSILON) { + const int sample_count = int(length / interval) + 2; + interval = length / (sample_count - 1); // Recalculate real interval length. + + Vector<Transform2D> frames; + frames.resize(sample_count); + + { + Transform2D *w = frames.ptrw(); + + for (int i = 0; i < sample_count; i++) { + w[i] = curve->sample_baked_with_rotation(i * interval, false); + } } - } - draw_polyline(_cached_draw_pts, get_tree()->get_debug_paths_color(), line_width, true); + const Transform2D *r = frames.ptr(); + // Draw curve segments + { + PackedVector2Array v2p; + v2p.resize(sample_count); + Vector2 *w = v2p.ptrw(); + + for (int i = 0; i < sample_count; i++) { + w[i] = r[i].get_origin(); + } + draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width, false); + } + + // Draw fish bones + { + PackedVector2Array v2p; + v2p.resize(3); + Vector2 *w = v2p.ptrw(); + + for (int i = 0; i < sample_count; i++) { + const Vector2 p = r[i].get_origin(); + const Vector2 side = r[i].columns[0]; + const Vector2 forward = r[i].columns[1]; + + // Fish Bone. + w[0] = p + (side - forward) * 5; + w[1] = p; + w[2] = p + (-side - forward) * 5; + + draw_polyline(v2p, get_tree()->get_debug_paths_color(), line_width * 0.5, false); + } + } + } } break; } } diff --git a/scene/2d/path_2d.h b/scene/2d/path_2d.h index 5e436fb9f6..935717605a 100644 --- a/scene/2d/path_2d.h +++ b/scene/2d/path_2d.h @@ -38,7 +38,6 @@ class Path2D : public Node2D { GDCLASS(Path2D, Node2D); Ref<Curve2D> curve; - Vector<Vector2> _cached_draw_pts; void _curve_changed(); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 0159e9f313..ced77b26ee 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -995,9 +995,11 @@ void TileMap::_recompute_rect_cache() { } } + bool changed = rect_cache != r_total; + rect_cache = r_total; - item_rect_changed(); + item_rect_changed(changed); rect_cache_dirty = false; #endif @@ -3838,7 +3840,7 @@ void TileMap::set_texture_repeat(CanvasItem::TextureRepeat p_texture_repeat) { } } -TypedArray<Vector2i> TileMap::get_surrounding_tiles(Vector2i coords) { +TypedArray<Vector2i> TileMap::get_surrounding_cells(Vector2i coords) { if (!tile_set.is_valid()) { return TypedArray<Vector2i>(); } @@ -4012,7 +4014,7 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("force_update", "layer"), &TileMap::force_update, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_surrounding_tiles", "coords"), &TileMap::get_surrounding_tiles); + ClassDB::bind_method(D_METHOD("get_surrounding_cells", "coords"), &TileMap::get_surrounding_cells); ClassDB::bind_method(D_METHOD("get_used_cells", "layer"), &TileMap::get_used_cells); ClassDB::bind_method(D_METHOD("get_used_rect"), &TileMap::get_used_rect); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 68a5d3c80b..19f0e5a553 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -400,7 +400,7 @@ public: void force_update(int p_layer = -1); // Helpers? - TypedArray<Vector2i> get_surrounding_tiles(Vector2i coords); + TypedArray<Vector2i> get_surrounding_cells(Vector2i coords); void draw_cells_outline(Control *p_control, RBSet<Vector2i> p_cells, Color p_color, Transform2D p_transform = Transform2D()); // Virtual function to modify the TileData at runtime diff --git a/scene/2d/touch_screen_button.cpp b/scene/2d/touch_screen_button.cpp index a02f322ef1..11b4718802 100644 --- a/scene/2d/touch_screen_button.cpp +++ b/scene/2d/touch_screen_button.cpp @@ -100,7 +100,7 @@ void TouchScreenButton::_notification(int p_what) { if (!is_inside_tree()) { return; } - if (!Engine::get_singleton()->is_editor_hint() && !!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())) && visibility == VISIBILITY_TOUCHSCREEN_ONLY) { + if (!Engine::get_singleton()->is_editor_hint() && !DisplayServer::get_singleton()->is_touchscreen_available() && visibility == VISIBILITY_TOUCHSCREEN_ONLY) { return; } @@ -137,7 +137,7 @@ void TouchScreenButton::_notification(int p_what) { } break; case NOTIFICATION_ENTER_TREE: { - if (!Engine::get_singleton()->is_editor_hint() && !!DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())) && visibility == VISIBILITY_TOUCHSCREEN_ONLY) { + if (!Engine::get_singleton()->is_editor_hint() && !DisplayServer::get_singleton()->is_touchscreen_available() && visibility == VISIBILITY_TOUCHSCREEN_ONLY) { return; } queue_redraw(); diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp index 304e56326d..2a50575749 100644 --- a/scene/3d/camera_3d.cpp +++ b/scene/3d/camera_3d.cpp @@ -112,6 +112,12 @@ void Camera3D::_notification(int p_what) { if (current || first_camera) { viewport->_camera_3d_set(this); } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + viewport->connect(SNAME("size_changed"), callable_mp((Node3D *)this, &Camera3D::update_gizmos)); + } +#endif } break; case NOTIFICATION_TRANSFORM_CHANGED: { @@ -133,6 +139,11 @@ void Camera3D::_notification(int p_what) { } if (viewport) { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + viewport->disconnect(SNAME("size_changed"), callable_mp((Node3D *)this, &Camera3D::update_gizmos)); + } +#endif viewport->_camera_3d_remove(this); viewport = nullptr; } diff --git a/scene/3d/light_3d.cpp b/scene/3d/light_3d.cpp index 198dba7811..3f43e718b8 100644 --- a/scene/3d/light_3d.cpp +++ b/scene/3d/light_3d.cpp @@ -461,7 +461,7 @@ Light3D::Light3D(RenderingServer::LightType p_type) { set_param(PARAM_SHADOW_PANCAKE_SIZE, 20.0); set_param(PARAM_SHADOW_OPACITY, 1.0); set_param(PARAM_SHADOW_BLUR, 1.0); - set_param(PARAM_SHADOW_BIAS, 0.03); + set_param(PARAM_SHADOW_BIAS, 0.1); set_param(PARAM_SHADOW_NORMAL_BIAS, 1.0); set_param(PARAM_TRANSMITTANCE_BIAS, 0.05); set_param(PARAM_SHADOW_FADE_START, 1); @@ -571,8 +571,8 @@ DirectionalLight3D::DirectionalLight3D() : Light3D(RenderingServer::LIGHT_DIRECTIONAL) { set_param(PARAM_SHADOW_MAX_DISTANCE, 100); set_param(PARAM_SHADOW_FADE_START, 0.8); - // Increase the default shadow bias to better suit most scenes. - set_param(PARAM_SHADOW_BIAS, 0.1); + // Increase the default shadow normal bias to better suit most scenes. + set_param(PARAM_SHADOW_NORMAL_BIAS, 2.0); set_param(PARAM_INTENSITY, 100000.0); // Specified in Lux, approximate mid-day sun. set_shadow_mode(SHADOW_PARALLEL_4_SPLITS); blend_splits = false; @@ -614,8 +614,6 @@ void OmniLight3D::_bind_methods() { OmniLight3D::OmniLight3D() : Light3D(RenderingServer::LIGHT_OMNI) { set_shadow_mode(SHADOW_CUBE); - // Increase the default shadow biases to better suit most scenes. - set_param(PARAM_SHADOW_BIAS, 0.2); } PackedStringArray SpotLight3D::get_configuration_warnings() const { @@ -639,3 +637,9 @@ void SpotLight3D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle", PROPERTY_HINT_RANGE, "0,180,0.01,degrees"), "set_param", "get_param", PARAM_SPOT_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::FLOAT, "spot_angle_attenuation", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_param", "get_param", PARAM_SPOT_ATTENUATION); } + +SpotLight3D::SpotLight3D() : + Light3D(RenderingServer::LIGHT_SPOT) { + // Decrease the default shadow bias to better suit most scenes. + set_param(PARAM_SHADOW_BIAS, 0.03); +} diff --git a/scene/3d/light_3d.h b/scene/3d/light_3d.h index 84d214030b..d2dfa32aac 100644 --- a/scene/3d/light_3d.h +++ b/scene/3d/light_3d.h @@ -233,8 +233,7 @@ protected: public: PackedStringArray get_configuration_warnings() const override; - SpotLight3D() : - Light3D(RenderingServer::LIGHT_SPOT) {} + SpotLight3D(); }; #endif // LIGHT_3D_H diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index f7baa7facc..4800a83255 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -1001,6 +1001,7 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started) { double delta = p_delta * speed_scale * cd.speed_scale; double next_pos = cd.pos + delta; + bool backwards = signbit(delta); // Negative zero means playing backwards too. real_t len = cd.from->animation->get_length(); Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE; @@ -1012,23 +1013,7 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, } else if (next_pos > len) { next_pos = len; } - - bool backwards = signbit(delta); // Negative zero means playing backwards too - delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here) - - if (&cd == &playback.current) { - if (!backwards && cd.pos <= len && next_pos == len) { - //playback finished - end_reached = true; - end_notify = cd.pos < len; // Notify only if not already at the end - } - - if (backwards && cd.pos >= 0 && next_pos == 0) { - //playback finished - end_reached = true; - end_notify = cd.pos > 0; // Notify only if not already at the beginning - } - } + delta = next_pos - cd.pos; // Fix delta (after determination of backwards because negative zero is lost here). } break; case Animation::LOOP_LINEAR: { @@ -1057,8 +1042,28 @@ void AnimationPlayer::_animation_process_data(PlaybackData &cd, double p_delta, break; } - _animation_process_animation(cd.from, cd.pos, next_pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, looped_flag); + double prev_pos = cd.pos; // The animation may be changed during process, so it is safer that the state is changed before process. cd.pos = next_pos; + + AnimationData *prev_from = cd.from; + _animation_process_animation(cd.from, prev_pos, cd.pos, delta, p_blend, &cd == &playback.current, p_seeked, p_started, looped_flag); + + // End detection. + if (cd.from->animation->get_loop_mode() == Animation::LOOP_NONE) { + if (prev_from != playback.current.from) { + return; // Animation has been changed in the process (may be caused by method track), abort process. + } + if (!backwards && prev_pos <= len && next_pos == len) { + // Playback finished. + end_reached = true; + end_notify = prev_pos < len; // Notify only if not already at the end. + } + if (backwards && prev_pos >= 0 && next_pos == 0) { + // Playback finished. + end_reached = true; + end_notify = prev_pos > 0; // Notify only if not already at the beginning. + } + } } void AnimationPlayer::_animation_process2(double p_delta, bool p_started) { @@ -1066,23 +1071,25 @@ void AnimationPlayer::_animation_process2(double p_delta, bool p_started) { accum_pass++; - _animation_process_data(c.current, p_delta, 1.0f, c.seeked, p_started); + bool seeked = c.seeked; // The animation may be changed during process, so it is safer that the state is changed before process. if (p_delta != 0) { c.seeked = false; } + _animation_process_data(c.current, p_delta, 1.0f, seeked, p_started); + List<Blend>::Element *prev = nullptr; for (List<Blend>::Element *E = c.blend.back(); E; E = prev) { Blend &b = E->get(); float blend = b.blend_left / b.blend_time; - _animation_process_data(b.data, p_delta, blend, false, false); - b.blend_left -= Math::absf(speed_scale * p_delta); - prev = E->prev(); if (b.blend_left < 0) { c.blend.erase(E); } + // The effect of animation changes during blending is unknown... + // In that case, we recommends to use method call mode "deferred", not "immediate". + _animation_process_data(b.data, p_delta, blend, false, false); } } @@ -1123,8 +1130,6 @@ void AnimationPlayer::_animation_update_transforms() { } } - cache_update_size = 0; - for (int i = 0; i < cache_update_prop_size; i++) { TrackNodeCache::PropertyAnim *pa = cache_update_prop[i]; @@ -1186,29 +1191,35 @@ void AnimationPlayer::_animation_update_transforms() { } } - cache_update_prop_size = 0; - for (int i = 0; i < cache_update_bezier_size; i++) { TrackNodeCache::BezierAnim *ba = cache_update_bezier[i]; ERR_CONTINUE(ba->accum_pass != accum_pass); ba->object->set_indexed(ba->bezier_property, ba->bezier_accum); } - - cache_update_bezier_size = 0; } void AnimationPlayer::_animation_process(double p_delta) { if (playback.current.from) { end_reached = false; end_notify = false; - _animation_process2(p_delta, playback.started); + bool started = playback.started; // The animation may be changed during process, so it is safer that the state is changed before process. if (playback.started) { playback.started = false; } + cache_update_size = 0; + cache_update_prop_size = 0; + cache_update_bezier_size = 0; + + AnimationData *prev_from = playback.current.from; + _animation_process2(p_delta, started); + if (prev_from != playback.current.from) { + return; // Animation has been changed in the process (may be caused by method track), abort process. + } _animation_update_transforms(); + if (end_reached) { if (queued.size()) { String old = playback.assigned; diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index bd9b918900..c2d584b52f 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -1031,7 +1031,9 @@ void AnimationTree::_process_graph(double p_delta) { } NodePath path = a->track_get_path(i); - ERR_CONTINUE(!track_cache.has(path)); + if (!track_cache.has(path)) { + continue; // No path, but avoid error spamming. + } TrackCache *track = track_cache[path]; ERR_CONTINUE(!state.track_map.has(path)); diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index 35ba49563c..28eedcbe13 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -220,8 +220,12 @@ void SceneDebugger::_save_node(ObjectID id, const String &p_path) { Node *node = Object::cast_to<Node>(ObjectDB::get_instance(id)); ERR_FAIL_COND(!node); +#ifdef TOOLS_ENABLED HashMap<const Node *, Node *> duplimap; Node *copy = node->duplicate_from_editor(duplimap); +#else + Node *copy = node->duplicate(); +#endif // Handle Unique Nodes. for (int i = 0; i < copy->get_child_count(false); i++) { diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index e90a6a69ab..50ffc0509c 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -257,36 +257,36 @@ bool Control::_set(const StringName &p_name, const Variant &p_value) { if (p_value.get_type() == Variant::NIL || (p_value.get_type() == Variant::OBJECT && (Object *)p_value == nullptr)) { if (name.begins_with("theme_override_icons/")) { String dname = name.get_slicec('/', 1); - if (data.icon_override.has(dname)) { - data.icon_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + if (data.theme_icon_override.has(dname)) { + data.theme_icon_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - data.icon_override.erase(dname); + data.theme_icon_override.erase(dname); _notify_theme_override_changed(); } else if (name.begins_with("theme_override_styles/")) { String dname = name.get_slicec('/', 1); - if (data.style_override.has(dname)) { - data.style_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + if (data.theme_style_override.has(dname)) { + data.theme_style_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - data.style_override.erase(dname); + data.theme_style_override.erase(dname); _notify_theme_override_changed(); } else if (name.begins_with("theme_override_fonts/")) { String dname = name.get_slicec('/', 1); - if (data.font_override.has(dname)) { - data.font_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + if (data.theme_font_override.has(dname)) { + data.theme_font_override[dname]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - data.font_override.erase(dname); + data.theme_font_override.erase(dname); _notify_theme_override_changed(); } else if (name.begins_with("theme_override_font_sizes/")) { String dname = name.get_slicec('/', 1); - data.font_size_override.erase(dname); + data.theme_font_size_override.erase(dname); _notify_theme_override_changed(); } else if (name.begins_with("theme_override_colors/")) { String dname = name.get_slicec('/', 1); - data.color_override.erase(dname); + data.theme_color_override.erase(dname); _notify_theme_override_changed(); } else if (name.begins_with("theme_override_constants/")) { String dname = name.get_slicec('/', 1); - data.constant_override.erase(dname); + data.theme_constant_override.erase(dname); _notify_theme_override_changed(); } else { return false; @@ -326,22 +326,22 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const { if (sname.begins_with("theme_override_icons/")) { String name = sname.get_slicec('/', 1); - r_ret = data.icon_override.has(name) ? Variant(data.icon_override[name]) : Variant(); + r_ret = data.theme_icon_override.has(name) ? Variant(data.theme_icon_override[name]) : Variant(); } else if (sname.begins_with("theme_override_styles/")) { String name = sname.get_slicec('/', 1); - r_ret = data.style_override.has(name) ? Variant(data.style_override[name]) : Variant(); + r_ret = data.theme_style_override.has(name) ? Variant(data.theme_style_override[name]) : Variant(); } else if (sname.begins_with("theme_override_fonts/")) { String name = sname.get_slicec('/', 1); - r_ret = data.font_override.has(name) ? Variant(data.font_override[name]) : Variant(); + r_ret = data.theme_font_override.has(name) ? Variant(data.theme_font_override[name]) : Variant(); } else if (sname.begins_with("theme_override_font_sizes/")) { String name = sname.get_slicec('/', 1); - r_ret = data.font_size_override.has(name) ? Variant(data.font_size_override[name]) : Variant(); + r_ret = data.theme_font_size_override.has(name) ? Variant(data.theme_font_size_override[name]) : Variant(); } else if (sname.begins_with("theme_override_colors/")) { String name = sname.get_slicec('/', 1); - r_ret = data.color_override.has(name) ? Variant(data.color_override[name]) : Variant(); + r_ret = data.theme_color_override.has(name) ? Variant(data.theme_color_override[name]) : Variant(); } else if (sname.begins_with("theme_override_constants/")) { String name = sname.get_slicec('/', 1); - r_ret = data.constant_override.has(name) ? Variant(data.constant_override[name]) : Variant(); + r_ret = data.theme_constant_override.has(name) ? Variant(data.theme_constant_override[name]) : Variant(); } else { return false; } @@ -350,16 +350,16 @@ bool Control::_get(const StringName &p_name, Variant &r_ret) const { } void Control::_get_property_list(List<PropertyInfo> *p_list) const { - Ref<Theme> theme = ThemeDB::get_singleton()->get_default_theme(); + Ref<Theme> default_theme = ThemeDB::get_singleton()->get_default_theme(); p_list->push_back(PropertyInfo(Variant::NIL, TTRC("Theme Overrides"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP)); { List<StringName> names; - theme->get_color_list(get_class_name(), &names); + default_theme->get_color_list(get_class_name(), &names); for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.color_override.has(E)) { + if (data.theme_color_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } @@ -368,10 +368,10 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { } { List<StringName> names; - theme->get_constant_list(get_class_name(), &names); + default_theme->get_constant_list(get_class_name(), &names); for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.constant_override.has(E)) { + if (data.theme_constant_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } @@ -380,10 +380,10 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { } { List<StringName> names; - theme->get_font_list(get_class_name(), &names); + default_theme->get_font_list(get_class_name(), &names); for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.font_override.has(E)) { + if (data.theme_font_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } @@ -392,10 +392,10 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { } { List<StringName> names; - theme->get_font_size_list(get_class_name(), &names); + default_theme->get_font_size_list(get_class_name(), &names); for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.font_size_override.has(E)) { + if (data.theme_font_size_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } @@ -404,10 +404,10 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { } { List<StringName> names; - theme->get_icon_list(get_class_name(), &names); + default_theme->get_icon_list(get_class_name(), &names); for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.icon_override.has(E)) { + if (data.theme_icon_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } @@ -416,10 +416,10 @@ void Control::_get_property_list(List<PropertyInfo> *p_list) const { } { List<StringName> names; - theme->get_stylebox_list(get_class_name(), &names); + default_theme->get_stylebox_list(get_class_name(), &names); for (const StringName &E : names) { uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; - if (data.style_override.has(E)) { + if (data.theme_style_override.has(E)) { usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; } @@ -2381,7 +2381,7 @@ StringName Control::get_theme_type_variation() const { Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const Ref<Texture2D> *tex = data.icon_override.getptr(p_name); + const Ref<Texture2D> *tex = data.theme_icon_override.getptr(p_name); if (tex) { return *tex; } @@ -2400,7 +2400,7 @@ Ref<Texture2D> Control::get_theme_icon(const StringName &p_name, const StringNam Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const Ref<StyleBox> *style = data.style_override.getptr(p_name); + const Ref<StyleBox> *style = data.theme_style_override.getptr(p_name); if (style) { return *style; } @@ -2419,7 +2419,7 @@ Ref<StyleBox> Control::get_theme_stylebox(const StringName &p_name, const String Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const { if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const Ref<Font> *font = data.font_override.getptr(p_name); + const Ref<Font> *font = data.theme_font_override.getptr(p_name); if (font) { return *font; } @@ -2438,7 +2438,7 @@ Ref<Font> Control::get_theme_font(const StringName &p_name, const StringName &p_ int Control::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const int *font_size = data.font_size_override.getptr(p_name); + const int *font_size = data.theme_font_size_override.getptr(p_name); if (font_size && (*font_size) > 0) { return *font_size; } @@ -2457,7 +2457,7 @@ int Control::get_theme_font_size(const StringName &p_name, const StringName &p_t Color Control::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const { if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const Color *color = data.color_override.getptr(p_name); + const Color *color = data.theme_color_override.getptr(p_name); if (color) { return *color; } @@ -2476,7 +2476,7 @@ Color Control::get_theme_color(const StringName &p_name, const StringName &p_the int Control::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == data.theme_type_variation) { - const int *constant = data.constant_override.getptr(p_name); + const int *constant = data.theme_constant_override.getptr(p_name); if (constant) { return *constant; } @@ -2570,123 +2570,123 @@ bool Control::has_theme_constant(const StringName &p_name, const StringName &p_t void Control::add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon) { ERR_FAIL_COND(!p_icon.is_valid()); - if (data.icon_override.has(p_name)) { - data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + if (data.theme_icon_override.has(p_name)) { + data.theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - data.icon_override[p_name] = p_icon; - data.icon_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + data.theme_icon_override[p_name] = p_icon; + data.theme_icon_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); _notify_theme_override_changed(); } void Control::add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) { ERR_FAIL_COND(!p_style.is_valid()); - if (data.style_override.has(p_name)) { - data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + if (data.theme_style_override.has(p_name)) { + data.theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - data.style_override[p_name] = p_style; - data.style_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + data.theme_style_override[p_name] = p_style; + data.theme_style_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); _notify_theme_override_changed(); } void Control::add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font) { ERR_FAIL_COND(!p_font.is_valid()); - if (data.font_override.has(p_name)) { - data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + if (data.theme_font_override.has(p_name)) { + data.theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - data.font_override[p_name] = p_font; - data.font_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + data.theme_font_override[p_name] = p_font; + data.theme_font_override[p_name]->connect("changed", callable_mp(this, &Control::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); _notify_theme_override_changed(); } void Control::add_theme_font_size_override(const StringName &p_name, int p_font_size) { - data.font_size_override[p_name] = p_font_size; + data.theme_font_size_override[p_name] = p_font_size; _notify_theme_override_changed(); } void Control::add_theme_color_override(const StringName &p_name, const Color &p_color) { - data.color_override[p_name] = p_color; + data.theme_color_override[p_name] = p_color; _notify_theme_override_changed(); } void Control::add_theme_constant_override(const StringName &p_name, int p_constant) { - data.constant_override[p_name] = p_constant; + data.theme_constant_override[p_name] = p_constant; _notify_theme_override_changed(); } void Control::remove_theme_icon_override(const StringName &p_name) { - if (data.icon_override.has(p_name)) { - data.icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + if (data.theme_icon_override.has(p_name)) { + data.theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - data.icon_override.erase(p_name); + data.theme_icon_override.erase(p_name); _notify_theme_override_changed(); } void Control::remove_theme_style_override(const StringName &p_name) { - if (data.style_override.has(p_name)) { - data.style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + if (data.theme_style_override.has(p_name)) { + data.theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - data.style_override.erase(p_name); + data.theme_style_override.erase(p_name); _notify_theme_override_changed(); } void Control::remove_theme_font_override(const StringName &p_name) { - if (data.font_override.has(p_name)) { - data.font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); + if (data.theme_font_override.has(p_name)) { + data.theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - data.font_override.erase(p_name); + data.theme_font_override.erase(p_name); _notify_theme_override_changed(); } void Control::remove_theme_font_size_override(const StringName &p_name) { - data.font_size_override.erase(p_name); + data.theme_font_size_override.erase(p_name); _notify_theme_override_changed(); } void Control::remove_theme_color_override(const StringName &p_name) { - data.color_override.erase(p_name); + data.theme_color_override.erase(p_name); _notify_theme_override_changed(); } void Control::remove_theme_constant_override(const StringName &p_name) { - data.constant_override.erase(p_name); + data.theme_constant_override.erase(p_name); _notify_theme_override_changed(); } bool Control::has_theme_icon_override(const StringName &p_name) const { - const Ref<Texture2D> *tex = data.icon_override.getptr(p_name); + const Ref<Texture2D> *tex = data.theme_icon_override.getptr(p_name); return tex != nullptr; } bool Control::has_theme_stylebox_override(const StringName &p_name) const { - const Ref<StyleBox> *style = data.style_override.getptr(p_name); + const Ref<StyleBox> *style = data.theme_style_override.getptr(p_name); return style != nullptr; } bool Control::has_theme_font_override(const StringName &p_name) const { - const Ref<Font> *font = data.font_override.getptr(p_name); + const Ref<Font> *font = data.theme_font_override.getptr(p_name); return font != nullptr; } bool Control::has_theme_font_size_override(const StringName &p_name) const { - const int *font_size = data.font_size_override.getptr(p_name); + const int *font_size = data.theme_font_size_override.getptr(p_name); return font_size != nullptr; } bool Control::has_theme_color_override(const StringName &p_name) const { - const Color *color = data.color_override.getptr(p_name); + const Color *color = data.theme_color_override.getptr(p_name); return color != nullptr; } bool Control::has_theme_constant_override(const StringName &p_name) const { - const int *constant = data.constant_override.getptr(p_name); + const int *constant = data.theme_constant_override.getptr(p_name); return constant != nullptr; } @@ -3359,21 +3359,21 @@ Control::~Control() { memdelete(data.theme_owner); // Resources need to be disconnected. - for (KeyValue<StringName, Ref<Texture2D>> &E : data.icon_override) { + for (KeyValue<StringName, Ref<Texture2D>> &E : data.theme_icon_override) { E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - for (KeyValue<StringName, Ref<StyleBox>> &E : data.style_override) { + for (KeyValue<StringName, Ref<StyleBox>> &E : data.theme_style_override) { E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } - for (KeyValue<StringName, Ref<Font>> &E : data.font_override) { + for (KeyValue<StringName, Ref<Font>> &E : data.theme_font_override) { E.value->disconnect("changed", callable_mp(this, &Control::_notify_theme_override_changed)); } // Then override maps can be simply cleared. - data.icon_override.clear(); - data.style_override.clear(); - data.font_override.clear(); - data.font_size_override.clear(); - data.color_override.clear(); - data.constant_override.clear(); + data.theme_icon_override.clear(); + data.theme_style_override.clear(); + data.theme_font_override.clear(); + data.theme_font_size_override.clear(); + data.theme_color_override.clear(); + data.theme_constant_override.clear(); } diff --git a/scene/gui/control.h b/scene/gui/control.h index 3e9bb48a4a..12710f3a93 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -228,12 +228,12 @@ private: StringName theme_type_variation; bool bulk_theme_override = false; - Theme::ThemeIconMap icon_override; - Theme::ThemeStyleMap style_override; - Theme::ThemeFontMap font_override; - Theme::ThemeFontSizeMap font_size_override; - Theme::ThemeColorMap color_override; - Theme::ThemeConstantMap constant_override; + Theme::ThemeIconMap theme_icon_override; + Theme::ThemeStyleMap theme_style_override; + Theme::ThemeFontMap theme_font_override; + Theme::ThemeFontSizeMap theme_font_size_override; + Theme::ThemeColorMap theme_color_override; + Theme::ThemeConstantMap theme_constant_override; mutable HashMap<StringName, Theme::ThemeIconMap> theme_icon_cache; mutable HashMap<StringName, Theme::ThemeStyleMap> theme_style_cache; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index ab74979777..5d8e106e26 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -221,7 +221,7 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { Rect2 safe_area = this_rect; safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; - safe_area.size.y = items[p_over]._height_cache; + safe_area.size.y = items[p_over]._height_cache + theme_cache.v_separation; DisplayServer::get_singleton()->window_set_popup_safe_rect(submenu_popup->get_window_id(), safe_area); // Make the position of the parent popup relative to submenu popup. diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 60d107cce6..f26e05518e 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1854,10 +1854,6 @@ void RichTextLabel::_notification(int p_what) { } Control::CursorShape RichTextLabel::get_cursor_shape(const Point2 &p_pos) const { - if (!underline_meta) { - return get_default_cursor_shape(); - } - if (selection.click_item) { return CURSOR_IBEAM; } diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 6899178885..a44ddff507 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -550,7 +550,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) { drag_node_accum = Vector2(); last_drag_node_accum = Vector2(); drag_node_from = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0); - drag_node_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + drag_node_touching = DisplayServer::get_singleton()->is_touchscreen_available(); drag_node_touching_deaccel = false; time_since_motion = 0; diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp index 531226f938..73d30b7568 100644 --- a/scene/gui/scroll_container.cpp +++ b/scene/gui/scroll_container.cpp @@ -164,8 +164,8 @@ void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) { } } - bool screen_is_touchscreen = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); - if (!screen_is_touchscreen) { + bool is_touchscreen_available = DisplayServer::get_singleton()->is_touchscreen_available(); + if (!is_touchscreen_available) { return; } diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index e7d704a281..a96d85dffa 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1108,8 +1108,9 @@ void TextEdit::_notification(int p_what) { int start = TS->shaped_text_get_range(rid).x; if (!clipped && !search_text.is_empty()) { // Search highhlight int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); + int search_text_len = search_text.length(); while (search_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text.length() + start); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1125,14 +1126,15 @@ void TextEdit::_notification(int p_what) { draw_rect(rect, search_result_border_color, false); } - search_text_col = _get_column_pos_of_word(search_text, str, search_flags, search_text_col + 1); + search_text_col = _get_column_pos_of_word(search_text, str, search_flags, search_text_col + search_text_len); } } if (!clipped && highlight_all_occurrences && !only_whitespaces_highlighted && !highlighted_text.is_empty()) { // Highlight int highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + int highlighted_text_len = highlighted_text.length(); while (highlighted_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text.length() + start); + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1147,15 +1149,16 @@ void TextEdit::_notification(int p_what) { draw_rect(rect, word_highlighted_color); } - highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + 1); + highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + highlighted_text_len); } } if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') { - int highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); - while (highlighted_word_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + lookup_symbol_word.length() + start); + int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); + int lookup_symbol_word_len = lookup_symbol_word.length(); + while (lookup_symbol_word_col != -1) { + Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (line_spacing / 2), sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1172,7 +1175,7 @@ void TextEdit::_notification(int p_what) { draw_rect(rect, color); } - highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_word_col + 1); + lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, lookup_symbol_word_col + lookup_symbol_word_len); } } } diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index c0990211aa..3996d2d65d 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -3671,7 +3671,7 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { drag_accum = 0; //last_drag_accum=0; drag_from = v_scroll->get_value(); - drag_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id())); + drag_touching = DisplayServer::get_singleton()->is_touchscreen_available(); drag_touching_deaccel = false; if (drag_touching) { set_physics_process_internal(true); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index aff2c594d9..c71c3e195b 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -40,6 +40,218 @@ #include "scene/theme/theme_db.h" #include "scene/theme/theme_owner.h" +// Dynamic properties. + +bool Window::_set(const StringName &p_name, const Variant &p_value) { + String name = p_name; + if (!name.begins_with("theme_override")) { + return false; + } + + if (p_value.get_type() == Variant::NIL || (p_value.get_type() == Variant::OBJECT && (Object *)p_value == nullptr)) { + if (name.begins_with("theme_override_icons/")) { + String dname = name.get_slicec('/', 1); + if (theme_icon_override.has(dname)) { + theme_icon_override[dname]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + theme_icon_override.erase(dname); + _notify_theme_override_changed(); + } else if (name.begins_with("theme_override_styles/")) { + String dname = name.get_slicec('/', 1); + if (theme_style_override.has(dname)) { + theme_style_override[dname]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + theme_style_override.erase(dname); + _notify_theme_override_changed(); + } else if (name.begins_with("theme_override_fonts/")) { + String dname = name.get_slicec('/', 1); + if (theme_font_override.has(dname)) { + theme_font_override[dname]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + theme_font_override.erase(dname); + _notify_theme_override_changed(); + } else if (name.begins_with("theme_override_font_sizes/")) { + String dname = name.get_slicec('/', 1); + theme_font_size_override.erase(dname); + _notify_theme_override_changed(); + } else if (name.begins_with("theme_override_colors/")) { + String dname = name.get_slicec('/', 1); + theme_color_override.erase(dname); + _notify_theme_override_changed(); + } else if (name.begins_with("theme_override_constants/")) { + String dname = name.get_slicec('/', 1); + theme_constant_override.erase(dname); + _notify_theme_override_changed(); + } else { + return false; + } + + } else { + if (name.begins_with("theme_override_icons/")) { + String dname = name.get_slicec('/', 1); + add_theme_icon_override(dname, p_value); + } else if (name.begins_with("theme_override_styles/")) { + String dname = name.get_slicec('/', 1); + add_theme_style_override(dname, p_value); + } else if (name.begins_with("theme_override_fonts/")) { + String dname = name.get_slicec('/', 1); + add_theme_font_override(dname, p_value); + } else if (name.begins_with("theme_override_font_sizes/")) { + String dname = name.get_slicec('/', 1); + add_theme_font_size_override(dname, p_value); + } else if (name.begins_with("theme_override_colors/")) { + String dname = name.get_slicec('/', 1); + add_theme_color_override(dname, p_value); + } else if (name.begins_with("theme_override_constants/")) { + String dname = name.get_slicec('/', 1); + add_theme_constant_override(dname, p_value); + } else { + return false; + } + } + return true; +} + +bool Window::_get(const StringName &p_name, Variant &r_ret) const { + String sname = p_name; + if (!sname.begins_with("theme_override")) { + return false; + } + + if (sname.begins_with("theme_override_icons/")) { + String name = sname.get_slicec('/', 1); + r_ret = theme_icon_override.has(name) ? Variant(theme_icon_override[name]) : Variant(); + } else if (sname.begins_with("theme_override_styles/")) { + String name = sname.get_slicec('/', 1); + r_ret = theme_style_override.has(name) ? Variant(theme_style_override[name]) : Variant(); + } else if (sname.begins_with("theme_override_fonts/")) { + String name = sname.get_slicec('/', 1); + r_ret = theme_font_override.has(name) ? Variant(theme_font_override[name]) : Variant(); + } else if (sname.begins_with("theme_override_font_sizes/")) { + String name = sname.get_slicec('/', 1); + r_ret = theme_font_size_override.has(name) ? Variant(theme_font_size_override[name]) : Variant(); + } else if (sname.begins_with("theme_override_colors/")) { + String name = sname.get_slicec('/', 1); + r_ret = theme_color_override.has(name) ? Variant(theme_color_override[name]) : Variant(); + } else if (sname.begins_with("theme_override_constants/")) { + String name = sname.get_slicec('/', 1); + r_ret = theme_constant_override.has(name) ? Variant(theme_constant_override[name]) : Variant(); + } else { + return false; + } + + return true; +} + +void Window::_get_property_list(List<PropertyInfo> *p_list) const { + Ref<Theme> default_theme = ThemeDB::get_singleton()->get_default_theme(); + + p_list->push_back(PropertyInfo(Variant::NIL, TTRC("Theme Overrides"), PROPERTY_HINT_NONE, "theme_override_", PROPERTY_USAGE_GROUP)); + + { + List<StringName> names; + default_theme->get_color_list(get_class_name(), &names); + for (const StringName &E : names) { + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + if (theme_color_override.has(E)) { + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + } + + p_list->push_back(PropertyInfo(Variant::COLOR, "theme_override_colors/" + E, PROPERTY_HINT_NONE, "", usage)); + } + } + { + List<StringName> names; + default_theme->get_constant_list(get_class_name(), &names); + for (const StringName &E : names) { + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + if (theme_constant_override.has(E)) { + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + } + + p_list->push_back(PropertyInfo(Variant::INT, "theme_override_constants/" + E, PROPERTY_HINT_RANGE, "-16384,16384", usage)); + } + } + { + List<StringName> names; + default_theme->get_font_list(get_class_name(), &names); + for (const StringName &E : names) { + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + if (theme_font_override.has(E)) { + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + } + + p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_fonts/" + E, PROPERTY_HINT_RESOURCE_TYPE, "Font", usage)); + } + } + { + List<StringName> names; + default_theme->get_font_size_list(get_class_name(), &names); + for (const StringName &E : names) { + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + if (theme_font_size_override.has(E)) { + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + } + + p_list->push_back(PropertyInfo(Variant::INT, "theme_override_font_sizes/" + E, PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px", usage)); + } + } + { + List<StringName> names; + default_theme->get_icon_list(get_class_name(), &names); + for (const StringName &E : names) { + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + if (theme_icon_override.has(E)) { + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + } + + p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_icons/" + E, PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", usage)); + } + } + { + List<StringName> names; + default_theme->get_stylebox_list(get_class_name(), &names); + for (const StringName &E : names) { + uint32_t usage = PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_CHECKABLE; + if (theme_style_override.has(E)) { + usage |= PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_CHECKED; + } + + p_list->push_back(PropertyInfo(Variant::OBJECT, "theme_override_styles/" + E, PROPERTY_HINT_RESOURCE_TYPE, "StyleBox", usage)); + } + } +} + +void Window::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "theme_type_variation") { + List<StringName> names; + + // Only the default theme and the project theme are used for the list of options. + // This is an imposed limitation to simplify the logic needed to leverage those options. + ThemeDB::get_singleton()->get_default_theme()->get_type_variation_list(get_class_name(), &names); + if (ThemeDB::get_singleton()->get_project_theme().is_valid()) { + ThemeDB::get_singleton()->get_project_theme()->get_type_variation_list(get_class_name(), &names); + } + names.sort_custom<StringName::AlphCompare>(); + + Vector<StringName> unique_names; + String hint_string; + for (const StringName &E : names) { + // Skip duplicate values. + if (unique_names.has(E)) { + continue; + } + + hint_string += String(E) + ","; + unique_names.append(E); + } + + p_property.hint_string = hint_string; + } +} + +// + void Window::set_title(const String &p_title) { title = p_title; @@ -106,9 +318,16 @@ void Window::reset_size() { set_size(Size2i()); } -Size2i Window::get_real_size() const { +Point2i Window::get_position_with_decorations() const { + if (window_id != DisplayServer::INVALID_WINDOW_ID) { + return DisplayServer::get_singleton()->window_get_position_with_decorations(window_id); + } + return position; +} + +Size2i Window::get_size_with_decorations() const { if (window_id != DisplayServer::INVALID_WINDOW_ID) { - return DisplayServer::get_singleton()->window_get_real_size(window_id); + return DisplayServer::get_singleton()->window_get_size_with_decorations(window_id); } return size; } @@ -1316,6 +1535,8 @@ void Window::remove_child_notify(Node *p_child) { } } +// Theming. + void Window::set_theme_owner_node(Node *p_node) { theme_owner->set_owner_node(p_node); } @@ -1369,6 +1590,12 @@ void Window::_theme_changed() { } } +void Window::_notify_theme_override_changed() { + if (!bulk_theme_override && is_inside_tree()) { + notification(NOTIFICATION_THEME_CHANGED); + } +} + void Window::_invalidate_theme_cache() { theme_icon_cache.clear(); theme_style_cache.clear(); @@ -1379,6 +1606,9 @@ void Window::_invalidate_theme_cache() { } void Window::_update_theme_item_cache() { + // Request an update on the next frame to reflect theme changes. + // Updating without a delay can cause a lot of lag. + child_controls_changed(); } void Window::set_theme_type_variation(const StringName &p_theme_type) { @@ -1392,7 +1622,16 @@ StringName Window::get_theme_type_variation() const { return theme_type_variation; } +/// Theme property lookup. + Ref<Texture2D> Window::get_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + const Ref<Texture2D> *tex = theme_icon_override.getptr(p_name); + if (tex) { + return *tex; + } + } + if (theme_icon_cache.has(p_theme_type) && theme_icon_cache[p_theme_type].has(p_name)) { return theme_icon_cache[p_theme_type][p_name]; } @@ -1405,6 +1644,13 @@ Ref<Texture2D> Window::get_theme_icon(const StringName &p_name, const StringName } Ref<StyleBox> Window::get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + const Ref<StyleBox> *style = theme_style_override.getptr(p_name); + if (style) { + return *style; + } + } + if (theme_style_cache.has(p_theme_type) && theme_style_cache[p_theme_type].has(p_name)) { return theme_style_cache[p_theme_type][p_name]; } @@ -1417,6 +1663,13 @@ Ref<StyleBox> Window::get_theme_stylebox(const StringName &p_name, const StringN } Ref<Font> Window::get_theme_font(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + const Ref<Font> *font = theme_font_override.getptr(p_name); + if (font) { + return *font; + } + } + if (theme_font_cache.has(p_theme_type) && theme_font_cache[p_theme_type].has(p_name)) { return theme_font_cache[p_theme_type][p_name]; } @@ -1429,6 +1682,13 @@ Ref<Font> Window::get_theme_font(const StringName &p_name, const StringName &p_t } int Window::get_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + const int *font_size = theme_font_size_override.getptr(p_name); + if (font_size && (*font_size) > 0) { + return *font_size; + } + } + if (theme_font_size_cache.has(p_theme_type) && theme_font_size_cache[p_theme_type].has(p_name)) { return theme_font_size_cache[p_theme_type][p_name]; } @@ -1441,6 +1701,13 @@ int Window::get_theme_font_size(const StringName &p_name, const StringName &p_th } Color Window::get_theme_color(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + const Color *color = theme_color_override.getptr(p_name); + if (color) { + return *color; + } + } + if (theme_color_cache.has(p_theme_type) && theme_color_cache[p_theme_type].has(p_name)) { return theme_color_cache[p_theme_type][p_name]; } @@ -1453,6 +1720,13 @@ Color Window::get_theme_color(const StringName &p_name, const StringName &p_them } int Window::get_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + const int *constant = theme_constant_override.getptr(p_name); + if (constant) { + return *constant; + } + } + if (theme_constant_cache.has(p_theme_type) && theme_constant_cache[p_theme_type].has(p_name)) { return theme_constant_cache[p_theme_type][p_name]; } @@ -1465,41 +1739,204 @@ int Window::get_theme_constant(const StringName &p_name, const StringName &p_the } bool Window::has_theme_icon(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + if (has_theme_icon_override(p_name)) { + return true; + } + } + List<StringName> theme_types; theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_ICON, p_name, theme_types); } bool Window::has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + if (has_theme_stylebox_override(p_name)) { + return true; + } + } + List<StringName> theme_types; theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_STYLEBOX, p_name, theme_types); } bool Window::has_theme_font(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + if (has_theme_font_override(p_name)) { + return true; + } + } + List<StringName> theme_types; theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT, p_name, theme_types); } bool Window::has_theme_font_size(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + if (has_theme_font_size_override(p_name)) { + return true; + } + } + List<StringName> theme_types; theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_FONT_SIZE, p_name, theme_types); } bool Window::has_theme_color(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + if (has_theme_color_override(p_name)) { + return true; + } + } + List<StringName> theme_types; theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_COLOR, p_name, theme_types); } bool Window::has_theme_constant(const StringName &p_name, const StringName &p_theme_type) const { + if (p_theme_type == StringName() || p_theme_type == get_class_name() || p_theme_type == theme_type_variation) { + if (has_theme_constant_override(p_name)) { + return true; + } + } + List<StringName> theme_types; theme_owner->get_theme_type_dependencies(this, p_theme_type, &theme_types); return theme_owner->has_theme_item_in_types(Theme::DATA_TYPE_CONSTANT, p_name, theme_types); } +/// Local property overrides. + +void Window::add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon) { + ERR_FAIL_COND(!p_icon.is_valid()); + + if (theme_icon_override.has(p_name)) { + theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + + theme_icon_override[p_name] = p_icon; + theme_icon_override[p_name]->connect("changed", callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + _notify_theme_override_changed(); +} + +void Window::add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style) { + ERR_FAIL_COND(!p_style.is_valid()); + + if (theme_style_override.has(p_name)) { + theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + + theme_style_override[p_name] = p_style; + theme_style_override[p_name]->connect("changed", callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + _notify_theme_override_changed(); +} + +void Window::add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font) { + ERR_FAIL_COND(!p_font.is_valid()); + + if (theme_font_override.has(p_name)) { + theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + + theme_font_override[p_name] = p_font; + theme_font_override[p_name]->connect("changed", callable_mp(this, &Window::_notify_theme_override_changed), CONNECT_REFERENCE_COUNTED); + _notify_theme_override_changed(); +} + +void Window::add_theme_font_size_override(const StringName &p_name, int p_font_size) { + theme_font_size_override[p_name] = p_font_size; + _notify_theme_override_changed(); +} + +void Window::add_theme_color_override(const StringName &p_name, const Color &p_color) { + theme_color_override[p_name] = p_color; + _notify_theme_override_changed(); +} + +void Window::add_theme_constant_override(const StringName &p_name, int p_constant) { + theme_constant_override[p_name] = p_constant; + _notify_theme_override_changed(); +} + +void Window::remove_theme_icon_override(const StringName &p_name) { + if (theme_icon_override.has(p_name)) { + theme_icon_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + + theme_icon_override.erase(p_name); + _notify_theme_override_changed(); +} + +void Window::remove_theme_style_override(const StringName &p_name) { + if (theme_style_override.has(p_name)) { + theme_style_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + + theme_style_override.erase(p_name); + _notify_theme_override_changed(); +} + +void Window::remove_theme_font_override(const StringName &p_name) { + if (theme_font_override.has(p_name)) { + theme_font_override[p_name]->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + + theme_font_override.erase(p_name); + _notify_theme_override_changed(); +} + +void Window::remove_theme_font_size_override(const StringName &p_name) { + theme_font_size_override.erase(p_name); + _notify_theme_override_changed(); +} + +void Window::remove_theme_color_override(const StringName &p_name) { + theme_color_override.erase(p_name); + _notify_theme_override_changed(); +} + +void Window::remove_theme_constant_override(const StringName &p_name) { + theme_constant_override.erase(p_name); + _notify_theme_override_changed(); +} + +bool Window::has_theme_icon_override(const StringName &p_name) const { + const Ref<Texture2D> *tex = theme_icon_override.getptr(p_name); + return tex != nullptr; +} + +bool Window::has_theme_stylebox_override(const StringName &p_name) const { + const Ref<StyleBox> *style = theme_style_override.getptr(p_name); + return style != nullptr; +} + +bool Window::has_theme_font_override(const StringName &p_name) const { + const Ref<Font> *font = theme_font_override.getptr(p_name); + return font != nullptr; +} + +bool Window::has_theme_font_size_override(const StringName &p_name) const { + const int *font_size = theme_font_size_override.getptr(p_name); + return font_size != nullptr; +} + +bool Window::has_theme_color_override(const StringName &p_name) const { + const Color *color = theme_color_override.getptr(p_name); + return color != nullptr; +} + +bool Window::has_theme_constant_override(const StringName &p_name) const { + const int *constant = theme_constant_override.getptr(p_name); + return constant != nullptr; +} + +/// Default theme properties. + float Window::get_theme_default_base_scale() const { return theme_owner->get_theme_default_base_scale(); } @@ -1512,6 +1949,21 @@ int Window::get_theme_default_font_size() const { return theme_owner->get_theme_default_font_size(); } +/// Bulk actions. + +void Window::begin_bulk_theme_override() { + bulk_theme_override = true; +} + +void Window::end_bulk_theme_override() { + ERR_FAIL_COND(!bulk_theme_override); + + bulk_theme_override = false; + _notify_theme_override_changed(); +} + +// + Rect2i Window::get_parent_rect() const { ERR_FAIL_COND_V(!is_inside_tree(), Rect2i()); if (is_embedded()) { @@ -1604,34 +2056,6 @@ bool Window::is_auto_translating() const { return auto_translate; } -void Window::_validate_property(PropertyInfo &p_property) const { - if (p_property.name == "theme_type_variation") { - List<StringName> names; - - // Only the default theme and the project theme are used for the list of options. - // This is an imposed limitation to simplify the logic needed to leverage those options. - ThemeDB::get_singleton()->get_default_theme()->get_type_variation_list(get_class_name(), &names); - if (ThemeDB::get_singleton()->get_project_theme().is_valid()) { - ThemeDB::get_singleton()->get_project_theme()->get_type_variation_list(get_class_name(), &names); - } - names.sort_custom<StringName::AlphCompare>(); - - Vector<StringName> unique_names; - String hint_string; - for (const StringName &E : names) { - // Skip duplicate values. - if (unique_names.has(E)) { - continue; - } - - hint_string += String(E) + ","; - unique_names.append(E); - } - - p_property.hint_string = hint_string; - } -} - Transform2D Window::get_screen_transform() const { Transform2D embedder_transform; if (_get_embedder()) { @@ -1655,7 +2079,8 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("get_size"), &Window::get_size); ClassDB::bind_method(D_METHOD("reset_size"), &Window::reset_size); - ClassDB::bind_method(D_METHOD("get_real_size"), &Window::get_real_size); + ClassDB::bind_method(D_METHOD("get_position_with_decorations"), &Window::get_position_with_decorations); + ClassDB::bind_method(D_METHOD("get_size_with_decorations"), &Window::get_size_with_decorations); ClassDB::bind_method(D_METHOD("set_max_size", "max_size"), &Window::set_max_size); ClassDB::bind_method(D_METHOD("get_max_size"), &Window::get_max_size); @@ -1725,6 +2150,23 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("set_theme_type_variation", "theme_type"), &Window::set_theme_type_variation); ClassDB::bind_method(D_METHOD("get_theme_type_variation"), &Window::get_theme_type_variation); + ClassDB::bind_method(D_METHOD("begin_bulk_theme_override"), &Window::begin_bulk_theme_override); + ClassDB::bind_method(D_METHOD("end_bulk_theme_override"), &Window::end_bulk_theme_override); + + ClassDB::bind_method(D_METHOD("add_theme_icon_override", "name", "texture"), &Window::add_theme_icon_override); + ClassDB::bind_method(D_METHOD("add_theme_stylebox_override", "name", "stylebox"), &Window::add_theme_style_override); + ClassDB::bind_method(D_METHOD("add_theme_font_override", "name", "font"), &Window::add_theme_font_override); + ClassDB::bind_method(D_METHOD("add_theme_font_size_override", "name", "font_size"), &Window::add_theme_font_size_override); + ClassDB::bind_method(D_METHOD("add_theme_color_override", "name", "color"), &Window::add_theme_color_override); + ClassDB::bind_method(D_METHOD("add_theme_constant_override", "name", "constant"), &Window::add_theme_constant_override); + + ClassDB::bind_method(D_METHOD("remove_theme_icon_override", "name"), &Window::remove_theme_icon_override); + ClassDB::bind_method(D_METHOD("remove_theme_stylebox_override", "name"), &Window::remove_theme_style_override); + ClassDB::bind_method(D_METHOD("remove_theme_font_override", "name"), &Window::remove_theme_font_override); + ClassDB::bind_method(D_METHOD("remove_theme_font_size_override", "name"), &Window::remove_theme_font_size_override); + ClassDB::bind_method(D_METHOD("remove_theme_color_override", "name"), &Window::remove_theme_color_override); + ClassDB::bind_method(D_METHOD("remove_theme_constant_override", "name"), &Window::remove_theme_constant_override); + ClassDB::bind_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Window::get_theme_icon, DEFVAL("")); ClassDB::bind_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Window::get_theme_stylebox, DEFVAL("")); ClassDB::bind_method(D_METHOD("get_theme_font", "name", "theme_type"), &Window::get_theme_font, DEFVAL("")); @@ -1732,6 +2174,13 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("get_theme_color", "name", "theme_type"), &Window::get_theme_color, DEFVAL("")); ClassDB::bind_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Window::get_theme_constant, DEFVAL("")); + ClassDB::bind_method(D_METHOD("has_theme_icon_override", "name"), &Window::has_theme_icon_override); + ClassDB::bind_method(D_METHOD("has_theme_stylebox_override", "name"), &Window::has_theme_stylebox_override); + ClassDB::bind_method(D_METHOD("has_theme_font_override", "name"), &Window::has_theme_font_override); + ClassDB::bind_method(D_METHOD("has_theme_font_size_override", "name"), &Window::has_theme_font_size_override); + ClassDB::bind_method(D_METHOD("has_theme_color_override", "name"), &Window::has_theme_color_override); + ClassDB::bind_method(D_METHOD("has_theme_constant_override", "name"), &Window::has_theme_constant_override); + ClassDB::bind_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Window::has_theme_icon, DEFVAL("")); ClassDB::bind_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Window::has_theme_stylebox, DEFVAL("")); ClassDB::bind_method(D_METHOD("has_theme_font", "name", "theme_type"), &Window::has_theme_font, DEFVAL("")); @@ -1785,13 +2234,13 @@ void Window::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_aspect", PROPERTY_HINT_ENUM, "Ignore,Keep,Keep Width,Keep Height,Expand"), "set_content_scale_aspect", "get_content_scale_aspect"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "content_scale_factor"), "set_content_scale_factor", "get_content_scale_factor"); + ADD_GROUP("Localization", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); + ADD_GROUP("Theme", "theme_"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "theme", PROPERTY_HINT_RESOURCE_TYPE, "Theme"), "set_theme", "get_theme"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "theme_type_variation", PROPERTY_HINT_ENUM_SUGGESTION), "set_theme_type_variation", "get_theme_type_variation"); - ADD_GROUP("Auto Translate", ""); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); - ADD_SIGNAL(MethodInfo("window_input", PropertyInfo(Variant::OBJECT, "event", PROPERTY_HINT_RESOURCE_TYPE, "InputEvent"))); ADD_SIGNAL(MethodInfo("files_dropped", PropertyInfo(Variant::PACKED_STRING_ARRAY, "files"))); ADD_SIGNAL(MethodInfo("mouse_entered")); @@ -1851,4 +2300,23 @@ Window::Window() { Window::~Window() { memdelete(theme_owner); + + // Resources need to be disconnected. + for (KeyValue<StringName, Ref<Texture2D>> &E : theme_icon_override) { + E.value->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + for (KeyValue<StringName, Ref<StyleBox>> &E : theme_style_override) { + E.value->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + for (KeyValue<StringName, Ref<Font>> &E : theme_font_override) { + E.value->disconnect("changed", callable_mp(this, &Window::_notify_theme_override_changed)); + } + + // Then override maps can be simply cleared. + theme_icon_override.clear(); + theme_style_override.clear(); + theme_font_override.clear(); + theme_font_size_override.clear(); + theme_color_override.clear(); + theme_constant_override.clear(); } diff --git a/scene/main/window.h b/scene/main/window.h index 03597b309a..5024b42587 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -140,6 +140,14 @@ private: Ref<Theme> theme; StringName theme_type_variation; + bool bulk_theme_override = false; + Theme::ThemeIconMap theme_icon_override; + Theme::ThemeStyleMap theme_style_override; + Theme::ThemeFontMap theme_font_override; + Theme::ThemeFontSizeMap theme_font_size_override; + Theme::ThemeColorMap theme_color_override; + Theme::ThemeConstantMap theme_constant_override; + mutable HashMap<StringName, Theme::ThemeIconMap> theme_icon_cache; mutable HashMap<StringName, Theme::ThemeStyleMap> theme_style_cache; mutable HashMap<StringName, Theme::ThemeFontMap> theme_font_cache; @@ -148,6 +156,7 @@ private: mutable HashMap<StringName, Theme::ThemeConstantMap> theme_constant_cache; void _theme_changed(); + void _notify_theme_override_changed(); void _invalidate_theme_cache(); Viewport *embedder = nullptr; @@ -173,6 +182,10 @@ protected: virtual Size2 _get_contents_minimum_size() const; static void _bind_methods(); void _notification(int p_what); + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; void _validate_property(PropertyInfo &p_property) const; virtual void add_child_notify(Node *p_child) override; @@ -198,7 +211,8 @@ public: Size2i get_size() const; void reset_size(); - Size2i get_real_size() const; + Point2i get_position_with_decorations() const; + Size2i get_size_with_decorations() const; void set_max_size(const Size2i &p_max_size); Size2i get_max_size() const; @@ -270,16 +284,6 @@ public: void popup_centered(const Size2i &p_minsize = Size2i()); void popup_centered_clamped(const Size2i &p_size = Size2i(), float p_fallback_ratio = 0.75); - void set_theme_owner_node(Node *p_node); - Node *get_theme_owner_node() const; - bool has_theme_owner_node() const; - - void set_theme(const Ref<Theme> &p_theme); - Ref<Theme> get_theme() const; - - void set_theme_type_variation(const StringName &p_theme_type); - StringName get_theme_type_variation() const; - Size2 get_contents_minimum_size() const; void grab_focus(); @@ -295,6 +299,35 @@ public: Rect2i get_usable_parent_rect() const; + // Theming. + + void set_theme_owner_node(Node *p_node); + Node *get_theme_owner_node() const; + bool has_theme_owner_node() const; + + void set_theme(const Ref<Theme> &p_theme); + Ref<Theme> get_theme() const; + + void set_theme_type_variation(const StringName &p_theme_type); + StringName get_theme_type_variation() const; + + void begin_bulk_theme_override(); + void end_bulk_theme_override(); + + void add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon); + void add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style); + void add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font); + void add_theme_font_size_override(const StringName &p_name, int p_font_size); + void add_theme_color_override(const StringName &p_name, const Color &p_color); + void add_theme_constant_override(const StringName &p_name, int p_constant); + + void remove_theme_icon_override(const StringName &p_name); + void remove_theme_style_override(const StringName &p_name); + void remove_theme_font_override(const StringName &p_name); + void remove_theme_font_size_override(const StringName &p_name); + void remove_theme_color_override(const StringName &p_name); + void remove_theme_constant_override(const StringName &p_name); + Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const; Ref<StyleBox> get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const; Ref<Font> get_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const; @@ -302,6 +335,13 @@ public: Color get_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const; int get_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const; + bool has_theme_icon_override(const StringName &p_name) const; + bool has_theme_stylebox_override(const StringName &p_name) const; + bool has_theme_font_override(const StringName &p_name) const; + bool has_theme_font_size_override(const StringName &p_name) const; + bool has_theme_color_override(const StringName &p_name) const; + bool has_theme_constant_override(const StringName &p_name) const; + bool has_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const; bool has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const; bool has_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const; @@ -313,6 +353,8 @@ public: Ref<Font> get_theme_default_font() const; int get_theme_default_font_size() const; + // + virtual Transform2D get_screen_transform() const override; Rect2i get_parent_rect() const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index ee45a8ea6f..58147b159a 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -827,14 +827,14 @@ void register_scene_types() { GDREGISTER_CLASS(ConvexPolygonShape3D); GDREGISTER_CLASS(ConcavePolygonShape3D); - ClassDB::register_class<SkeletonModificationStack3D>(); - ClassDB::register_class<SkeletonModification3D>(); - ClassDB::register_class<SkeletonModification3DLookAt>(); - ClassDB::register_class<SkeletonModification3DCCDIK>(); - ClassDB::register_class<SkeletonModification3DFABRIK>(); - ClassDB::register_class<SkeletonModification3DJiggle>(); - ClassDB::register_class<SkeletonModification3DTwoBoneIK>(); - ClassDB::register_class<SkeletonModification3DStackHolder>(); + GDREGISTER_CLASS(SkeletonModificationStack3D); + GDREGISTER_CLASS(SkeletonModification3D); + GDREGISTER_CLASS(SkeletonModification3DLookAt); + GDREGISTER_CLASS(SkeletonModification3DCCDIK); + GDREGISTER_CLASS(SkeletonModification3DFABRIK); + GDREGISTER_CLASS(SkeletonModification3DJiggle); + GDREGISTER_CLASS(SkeletonModification3DTwoBoneIK); + GDREGISTER_CLASS(SkeletonModification3DStackHolder); OS::get_singleton()->yield(); // may take time to init #endif // _3D_DISABLED diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index fa7a1f3dbf..bc2149a8c6 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -941,7 +941,7 @@ Transform2D Curve2D::_sample_posture(Interval p_interval) const { const Vector2 forward = forward_begin.slerp(forward_end, frac).normalized(); const Vector2 side = Vector2(-forward.y, forward.x); - return Transform2D(forward, side, Vector2(0.0, 0.0)); + return Transform2D(side, forward, Vector2(0.0, 0.0)); } Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const { diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index e457b2d377..a16d2c2072 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -1252,15 +1252,16 @@ void BaseMaterial3D::_update_shader() { } if (distance_fade != DISTANCE_FADE_DISABLED) { + // Use the slightly more expensive circular fade (distance to the object) instead of linear + // (Z distance), so that the fade is always the same regardless of the camera angle. if ((distance_fade == DISTANCE_FADE_OBJECT_DITHER || distance_fade == DISTANCE_FADE_PIXEL_DITHER)) { if (!RenderingServer::get_singleton()->is_low_end()) { code += " {\n"; if (distance_fade == DISTANCE_FADE_OBJECT_DITHER) { - code += " float fade_distance = abs((VIEW_MATRIX * MODEL_MATRIX[3]).z);\n"; - + code += " float fade_distance = length((VIEW_MATRIX * MODEL_MATRIX[3]));\n"; } else { - code += " float fade_distance = -VERTEX.z;\n"; + code += " float fade_distance = length(VERTEX);\n"; } // Use interleaved gradient noise, which is fast but still looks good. code += " const vec3 magic = vec3(0.06711056f, 0.00583715f, 52.9829189f);"; @@ -1274,7 +1275,7 @@ void BaseMaterial3D::_update_shader() { } } else { - code += " ALPHA*=clamp(smoothstep(distance_fade_min,distance_fade_max,-VERTEX.z),0.0,1.0);\n"; + code += " ALPHA *= clamp(smoothstep(distance_fade_min, distance_fade_max, length(VERTEX)), 0.0, 1.0);\n"; } } diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index f4b7f3d0b2..5316b524ba 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -450,7 +450,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { callable = callable.bindp(argptrs, binds.size()); } - cfrom->connect(snames[c.signal], callable, CONNECT_PERSIST | c.flags); + cfrom->connect(snames[c.signal], callable, CONNECT_PERSIST | c.flags | (p_edit_state == GEN_EDIT_STATE_MAIN ? 0 : CONNECT_INHERITED)); } //Node *s = ret_nodes[0]; diff --git a/scene/resources/shape_2d.cpp b/scene/resources/shape_2d.cpp index 413670d23e..87c6c36ee9 100644 --- a/scene/resources/shape_2d.cpp +++ b/scene/resources/shape_2d.cpp @@ -124,7 +124,6 @@ Shape2D::Shape2D(const RID &p_rid) { } Shape2D::~Shape2D() { - if (PhysicsServer2D::get_singleton() != nullptr) { - PhysicsServer2D::get_singleton()->free(shape); - } + ERR_FAIL_NULL(PhysicsServer2D::get_singleton()); + PhysicsServer2D::get_singleton()->free(shape); } diff --git a/scene/resources/shape_3d.cpp b/scene/resources/shape_3d.cpp index 44f21d2a48..7992ba9fd4 100644 --- a/scene/resources/shape_3d.cpp +++ b/scene/resources/shape_3d.cpp @@ -128,7 +128,6 @@ Shape3D::Shape3D(RID p_shape) : shape(p_shape) {} Shape3D::~Shape3D() { - if (PhysicsServer3D::get_singleton() != nullptr) { - PhysicsServer3D::get_singleton()->free(shape); - } + ERR_FAIL_NULL(PhysicsServer3D::get_singleton()); + PhysicsServer3D::get_singleton()->free(shape); } diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 52d7f66203..523cc714f0 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -367,8 +367,7 @@ float DisplayServer::screen_get_scale(int p_screen) const { return 1.0f; }; -bool DisplayServer::screen_is_touchscreen(int p_screen) const { - //return false; +bool DisplayServer::is_touchscreen_available() const { return Input::get_singleton() && Input::get_singleton()->is_emulating_touch_from_mouse(); } @@ -618,7 +617,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("screen_get_usable_rect", "screen"), &DisplayServer::screen_get_usable_rect, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_dpi", "screen"), &DisplayServer::screen_get_dpi, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_scale", "screen"), &DisplayServer::screen_get_scale, DEFVAL(SCREEN_OF_MAIN_WINDOW)); - ClassDB::bind_method(D_METHOD("screen_is_touchscreen", "screen"), &DisplayServer::screen_is_touchscreen, DEFVAL(SCREEN_OF_MAIN_WINDOW)); + ClassDB::bind_method(D_METHOD("is_touchscreen_available"), &DisplayServer::is_touchscreen_available, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_max_scale"), &DisplayServer::screen_get_max_scale); ClassDB::bind_method(D_METHOD("screen_get_refresh_rate", "screen"), &DisplayServer::screen_get_refresh_rate, DEFVAL(SCREEN_OF_MAIN_WINDOW)); @@ -643,6 +642,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("window_set_current_screen", "screen", "window_id"), &DisplayServer::window_set_current_screen, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_get_position", "window_id"), &DisplayServer::window_get_position, DEFVAL(MAIN_WINDOW_ID)); + ClassDB::bind_method(D_METHOD("window_get_position_with_decorations", "window_id"), &DisplayServer::window_get_position_with_decorations, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_set_position", "position", "window_id"), &DisplayServer::window_set_position, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_get_size", "window_id"), &DisplayServer::window_get_size, DEFVAL(MAIN_WINDOW_ID)); @@ -661,7 +661,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("window_get_min_size", "window_id"), &DisplayServer::window_get_min_size, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_set_min_size", "min_size", "window_id"), &DisplayServer::window_set_min_size, DEFVAL(MAIN_WINDOW_ID)); - ClassDB::bind_method(D_METHOD("window_get_real_size", "window_id"), &DisplayServer::window_get_real_size, DEFVAL(MAIN_WINDOW_ID)); + ClassDB::bind_method(D_METHOD("window_get_size_with_decorations", "window_id"), &DisplayServer::window_get_size_with_decorations, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_get_mode", "window_id"), &DisplayServer::window_get_mode, DEFVAL(MAIN_WINDOW_ID)); ClassDB::bind_method(D_METHOD("window_set_mode", "mode", "window_id"), &DisplayServer::window_set_mode, DEFVAL(MAIN_WINDOW_ID)); diff --git a/servers/display_server.h b/servers/display_server.h index a796377f88..4c36b0acb9 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -262,7 +262,7 @@ public: return scale; } virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0; - virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const; + virtual bool is_touchscreen_available() const; // Keep the ScreenOrientation enum values in sync with the `display/window/handheld/orientation` // project setting hint. @@ -353,6 +353,7 @@ public: virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) = 0; virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const = 0; + virtual Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const = 0; virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) = 0; virtual void window_set_transient(WindowID p_window, WindowID p_parent) = 0; @@ -366,7 +367,7 @@ public: virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) = 0; virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const = 0; - virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const = 0; // FIXME: Find clearer name for this. + virtual Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const = 0; virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) = 0; virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const = 0; diff --git a/servers/display_server_headless.h b/servers/display_server_headless.h index e1a27b6137..94b5f3c1e9 100644 --- a/servers/display_server_headless.h +++ b/servers/display_server_headless.h @@ -90,6 +90,7 @@ public: void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override {} Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override { return Point2i(); } + Point2i window_get_position_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override { return Point2i(); } void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override {} void window_set_transient(WindowID p_window, WindowID p_parent) override {} @@ -102,7 +103,7 @@ public: void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override {} Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override { return Size2i(); } - Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override { return Size2i(); } + Size2i window_get_size_with_decorations(WindowID p_window = MAIN_WINDOW_ID) const override { return Size2i(); } void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override {} WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override { return WINDOW_MODE_MINIMIZED; } diff --git a/servers/physics_2d/godot_shape_2d.cpp b/servers/physics_2d/godot_shape_2d.cpp index da414ae233..6823cb32c1 100644 --- a/servers/physics_2d/godot_shape_2d.cpp +++ b/servers/physics_2d/godot_shape_2d.cpp @@ -369,8 +369,9 @@ void GodotCapsuleShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_suppo Vector2 n = p_normal; real_t d = n.y; + real_t h = height * 0.5 - radius; // half-height of the rectangle part - if (Math::abs(d) < (1.0 - _SEGMENT_IS_VALID_SUPPORT_THRESHOLD)) { + if (h > 0 && Math::abs(d) < (1.0 - _SEGMENT_IS_VALID_SUPPORT_THRESHOLD)) { // make it flat n.y = 0.0; n.normalize(); @@ -378,13 +379,10 @@ void GodotCapsuleShape2D::get_supports(const Vector2 &p_normal, Vector2 *r_suppo r_amount = 2; r_supports[0] = n; - r_supports[0].y += height * 0.5 - radius; + r_supports[0].y += h; r_supports[1] = n; - r_supports[1].y -= height * 0.5 - radius; - + r_supports[1].y -= h; } else { - real_t h = height * 0.5 - radius; - n *= radius; n.y += (d > 0) ? h : -h; r_amount = 1; diff --git a/servers/physics_2d/godot_step_2d.cpp b/servers/physics_2d/godot_step_2d.cpp index 46718c8819..b7d79bc0e3 100644 --- a/servers/physics_2d/godot_step_2d.cpp +++ b/servers/physics_2d/godot_step_2d.cpp @@ -71,7 +71,7 @@ void GodotStep2D::_populate_island(GodotBody2D *p_body, LocalVector<GodotBody2D } } -void GodotStep2D::_setup_contraint(uint32_t p_constraint_index, void *p_userdata) { +void GodotStep2D::_setup_constraint(uint32_t p_constraint_index, void *p_userdata) { GodotConstraint2D *constraint = all_constraints[p_constraint_index]; constraint->setup(delta); } @@ -238,8 +238,8 @@ void GodotStep2D::step(GodotSpace2D *p_space, real_t p_delta) { /* SETUP CONSTRAINTS / PROCESS COLLISIONS */ - uint32_t total_contraint_count = all_constraints.size(); - WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep2D::_setup_contraint, nullptr, total_contraint_count, -1, true, SNAME("Physics2DConstraintSetup")); + uint32_t total_constraint_count = all_constraints.size(); + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep2D::_setup_constraint, nullptr, total_constraint_count, -1, true, SNAME("Physics2DConstraintSetup")); WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); { //profile diff --git a/servers/physics_2d/godot_step_2d.h b/servers/physics_2d/godot_step_2d.h index 9f8fdd6ce3..4b3b6fc966 100644 --- a/servers/physics_2d/godot_step_2d.h +++ b/servers/physics_2d/godot_step_2d.h @@ -47,7 +47,7 @@ class GodotStep2D { LocalVector<GodotConstraint2D *> all_constraints; void _populate_island(GodotBody2D *p_body, LocalVector<GodotBody2D *> &p_body_island, LocalVector<GodotConstraint2D *> &p_constraint_island); - void _setup_contraint(uint32_t p_constraint_index, void *p_userdata = nullptr); + void _setup_constraint(uint32_t p_constraint_index, void *p_userdata = nullptr); void _pre_solve_island(LocalVector<GodotConstraint2D *> &p_constraint_island) const; void _solve_island(uint32_t p_island_index, void *p_userdata = nullptr) const; void _check_suspend(LocalVector<GodotBody2D *> &p_body_island) const; diff --git a/servers/physics_3d/godot_collision_solver_3d_sat.cpp b/servers/physics_3d/godot_collision_solver_3d_sat.cpp index 96253cb452..8c78957789 100644 --- a/servers/physics_3d/godot_collision_solver_3d_sat.cpp +++ b/servers/physics_3d/godot_collision_solver_3d_sat.cpp @@ -1908,7 +1908,7 @@ static void _collision_cylinder_cylinder(const GodotShape3D *p_a, const Transfor } // Cylinder B end caps. - if (!separator.test_axis(cylinder_A_axis.normalized())) { + if (!separator.test_axis(cylinder_B_axis.normalized())) { return; } diff --git a/servers/physics_3d/godot_shape_3d.cpp b/servers/physics_3d/godot_shape_3d.cpp index 1443cd166b..22553bd3d8 100644 --- a/servers/physics_3d/godot_shape_3d.cpp +++ b/servers/physics_3d/godot_shape_3d.cpp @@ -521,8 +521,9 @@ void GodotCapsuleShape3D::get_supports(const Vector3 &p_normal, int p_max, Vecto Vector3 n = p_normal; real_t d = n.y; + real_t h = height * 0.5 - radius; // half-height of the cylinder part - if (Math::abs(d) < edge_support_threshold) { + if (h > 0 && Math::abs(d) < edge_support_threshold) { // make it flat n.y = 0.0; n.normalize(); @@ -531,13 +532,10 @@ void GodotCapsuleShape3D::get_supports(const Vector3 &p_normal, int p_max, Vecto r_amount = 2; r_type = FEATURE_EDGE; r_supports[0] = n; - r_supports[0].y += height * 0.5 - radius; + r_supports[0].y += h; r_supports[1] = n; - r_supports[1].y -= height * 0.5 - radius; - + r_supports[1].y -= h; } else { - real_t h = height * 0.5 - radius; - n *= radius; n.y += (d > 0) ? h : -h; r_amount = 1; diff --git a/servers/physics_3d/godot_step_3d.cpp b/servers/physics_3d/godot_step_3d.cpp index bfedcd29c0..634523d0b9 100644 --- a/servers/physics_3d/godot_step_3d.cpp +++ b/servers/physics_3d/godot_step_3d.cpp @@ -111,7 +111,7 @@ void GodotStep3D::_populate_island_soft_body(GodotSoftBody3D *p_soft_body, Local } } -void GodotStep3D::_setup_contraint(uint32_t p_constraint_index, void *p_userdata) { +void GodotStep3D::_setup_constraint(uint32_t p_constraint_index, void *p_userdata) { GodotConstraint3D *constraint = all_constraints[p_constraint_index]; constraint->setup(delta); } @@ -342,8 +342,8 @@ void GodotStep3D::step(GodotSpace3D *p_space, real_t p_delta) { /* SETUP CONSTRAINTS / PROCESS COLLISIONS */ - uint32_t total_contraint_count = all_constraints.size(); - WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep3D::_setup_contraint, nullptr, total_contraint_count, -1, true, SNAME("Physics3DConstraintSetup")); + uint32_t total_constraint_count = all_constraints.size(); + WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep3D::_setup_constraint, nullptr, total_constraint_count, -1, true, SNAME("Physics3DConstraintSetup")); WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task); { //profile diff --git a/servers/physics_3d/godot_step_3d.h b/servers/physics_3d/godot_step_3d.h index 189487757f..a0854a720c 100644 --- a/servers/physics_3d/godot_step_3d.h +++ b/servers/physics_3d/godot_step_3d.h @@ -48,7 +48,7 @@ class GodotStep3D { void _populate_island(GodotBody3D *p_body, LocalVector<GodotBody3D *> &p_body_island, LocalVector<GodotConstraint3D *> &p_constraint_island); void _populate_island_soft_body(GodotSoftBody3D *p_soft_body, LocalVector<GodotBody3D *> &p_body_island, LocalVector<GodotConstraint3D *> &p_constraint_island); - void _setup_contraint(uint32_t p_constraint_index, void *p_userdata = nullptr); + void _setup_constraint(uint32_t p_constraint_index, void *p_userdata = nullptr); void _pre_solve_island(LocalVector<GodotConstraint3D *> &p_constraint_island) const; void _solve_island(uint32_t p_island_index, void *p_userdata = nullptr); void _check_suspend(const LocalVector<GodotBody3D *> &p_body_island) const; diff --git a/servers/rendering/renderer_canvas_cull.cpp b/servers/rendering/renderer_canvas_cull.cpp index 075a21b818..33f1f38bf5 100644 --- a/servers/rendering/renderer_canvas_cull.cpp +++ b/servers/rendering/renderer_canvas_cull.cpp @@ -1075,18 +1075,36 @@ void RendererCanvasCull::canvas_item_add_polyline(RID p_item, const Vector<Point void RendererCanvasCull::canvas_item_add_multiline(RID p_item, const Vector<Point2> &p_points, const Vector<Color> &p_colors, float p_width) { ERR_FAIL_COND(p_points.size() < 2); - Item *canvas_item = canvas_item_owner.get_or_null(p_item); - ERR_FAIL_COND(!canvas_item); - - Item::CommandPolygon *pline = canvas_item->alloc_command<Item::CommandPolygon>(); - ERR_FAIL_COND(!pline); - if (true || p_width <= 1) { -#define TODO make thick lines possible + // TODO: `canvas_item_add_line`(`multiline`, `polyline`) share logic, should factor out. + if (p_width <= 1) { + Item *canvas_item = canvas_item_owner.get_or_null(p_item); + ERR_FAIL_COND(!canvas_item); + Item::CommandPolygon *pline = canvas_item->alloc_command<Item::CommandPolygon>(); + ERR_FAIL_COND(!pline); pline->primitive = RS::PRIMITIVE_LINES; pline->polygon.create(Vector<int>(), p_points, p_colors); } else { + if (p_colors.size() == 1) { + Color color = p_colors[0]; + for (int i = 0; i < p_points.size() >> 1; i++) { + Vector2 from = p_points[i * 2 + 0]; + Vector2 to = p_points[i * 2 + 1]; + + canvas_item_add_line(p_item, from, to, color, p_width); + } + } else if (p_colors.size() == p_points.size() >> 1) { + for (int i = 0; i < p_points.size() >> 1; i++) { + Color color = p_colors[i]; + Vector2 from = p_points[i * 2 + 0]; + Vector2 to = p_points[i * 2 + 1]; + + canvas_item_add_line(p_item, from, to, color, p_width); + } + } else { + ERR_FAIL_MSG("Length of p_colors is invalid."); + } } } diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index 0acd48c22a..36d26ad783 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -1661,7 +1661,7 @@ void SkyRD::update_res_buffers(RID p_env, uint32_t p_view_count, const Projectio projections = &camera; } - sky_transform = p_transform.basis * sky_transform; + sky_transform = sky_transform * p_transform.basis; if (shader_data->uses_quarter_res) { PipelineCacheRD *pipeline = &shader_data->pipelines[view_count > 1 ? SKY_VERSION_QUARTER_RES_MULTIVIEW : SKY_VERSION_QUARTER_RES]; @@ -1754,7 +1754,7 @@ void SkyRD::draw(RD::DrawListID p_draw_list, RID p_env, RID p_fb, uint32_t p_vie projections = &camera; } - sky_transform = p_transform.basis * sky_transform; + sky_transform = sky_transform * p_transform.basis; PipelineCacheRD *pipeline = &shader_data->pipelines[view_count > 1 ? SKY_VERSION_BACKGROUND_MULTIVIEW : SKY_VERSION_BACKGROUND]; diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index cc8238a8dd..d41daa18b4 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -398,7 +398,7 @@ void RendererCanvasRenderRD::_bind_canvas_texture(RD::DrawListID p_draw_list, RI r_last_texture = p_texture; } -void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RD::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, Light *p_lights, PipelineVariants *p_pipeline_variants) { +void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RD::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used) { //create an empty push constant RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton(); RendererRD::MeshStorage *mesh_storage = RendererRD::MeshStorage::get_singleton(); @@ -833,6 +833,9 @@ void RendererCanvasRenderRD::_render_item(RD::DrawListID p_draw_list, RID p_rend } else { particles_storage->particles_set_canvas_sdf_collision(pt->particles, false, Transform2D(), Rect2(), RID()); } + + // Signal that SDF texture needs to be updated. + r_sdf_used |= particles_storage->particles_has_collision(pt->particles); } if (mesh.is_null()) { @@ -1045,7 +1048,7 @@ RID RendererCanvasRenderRD::_create_base_uniform_set(RID p_to_render_target, boo return uniform_set; } -void RendererCanvasRenderRD::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool p_to_backbuffer) { +void RendererCanvasRenderRD::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer) { RendererRD::MaterialStorage *material_storage = RendererRD::MaterialStorage::get_singleton(); RendererRD::TextureStorage *texture_storage = RendererRD::TextureStorage::get_singleton(); @@ -1142,7 +1145,7 @@ void RendererCanvasRenderRD::_render_items(RID p_to_render_target, int p_item_co } } - _render_item(draw_list, p_to_render_target, ci, fb_format, canvas_transform_inverse, current_clip, p_lights, pipeline_variants); + _render_item(draw_list, p_to_render_target, ci, fb_format, canvas_transform_inverse, current_clip, p_lights, pipeline_variants, r_sdf_used); prev_material = material; } @@ -1440,7 +1443,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p update_skeletons = false; } - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used); item_count = 0; if (ci->canvas_group_owner->canvas_group->mode != RS::CANVAS_GROUP_MODE_TRANSPARENT) { @@ -1472,7 +1475,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p update_skeletons = false; } - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, true); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true); item_count = 0; if (ci->canvas_group->blur_mipmaps) { @@ -1491,7 +1494,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p update_skeletons = false; } - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used); item_count = 0; texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps); @@ -1517,7 +1520,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p update_skeletons = false; } - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false); //then reset item_count = 0; } diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h index 3fff574098..6e876b1297 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h @@ -421,8 +421,8 @@ class RendererCanvasRenderRD : public RendererCanvasRender { RID _create_base_uniform_set(RID p_to_render_target, bool p_backbuffer); inline void _bind_canvas_texture(RD::DrawListID p_draw_list, RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, RID &r_last_texture, PushConstant &push_constant, Size2 &r_texpixel_size); //recursive, so regular inline used instead. - void _render_item(RenderingDevice::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RenderingDevice::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, Light *p_lights, PipelineVariants *p_pipeline_variants); - void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool p_to_backbuffer = false); + void _render_item(RenderingDevice::DrawListID p_draw_list, RID p_render_target, const Item *p_item, RenderingDevice::FramebufferFormatID p_framebuffer_format, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, Light *p_lights, PipelineVariants *p_pipeline_variants, bool &r_sdf_used); + void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false); _FORCE_INLINE_ void _update_transform_2d_to_mat2x4(const Transform2D &p_transform, float *p_mat2x4); _FORCE_INLINE_ void _update_transform_2d_to_mat2x3(const Transform2D &p_transform, float *p_mat2x3); diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 0f4dda3f11..1b5665855f 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -1419,10 +1419,13 @@ void RendererSceneRenderRD::init() { cull_argument.set_page_pool(&cull_argument_pool); bool can_use_storage = _render_buffers_can_be_storage(); + bool can_use_vrs = is_vrs_supported(); bokeh_dof = memnew(RendererRD::BokehDOF(!can_use_storage)); copy_effects = memnew(RendererRD::CopyEffects(!can_use_storage)); tone_mapper = memnew(RendererRD::ToneMapper); - vrs = memnew(RendererRD::VRS); + if (can_use_vrs) { + vrs = memnew(RendererRD::VRS); + } if (can_use_storage) { fsr = memnew(RendererRD::FSR); } diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 71f4f3ad11..503a25184e 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -445,7 +445,14 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) for (int i = 0; i < p_surface.bone_aabbs.size(); i++) { const AABB &bone = p_surface.bone_aabbs[i]; if (bone.has_volume()) { - mesh->bone_aabbs.write[i].merge_with(bone); + AABB &mesh_bone = mesh->bone_aabbs.write[i]; + if (mesh_bone != AABB()) { + // Already initialized, merge AABBs. + mesh_bone.merge_with(bone); + } else { + // Not yet initialized, copy the bone AABB. + mesh_bone = bone; + } } } mesh->aabb.merge_with(p_surface.aabb); diff --git a/tests/core/os/test_os.h b/tests/core/os/test_os.h index c46da5e156..086f0b9b0c 100644 --- a/tests/core/os/test_os.h +++ b/tests/core/os/test_os.h @@ -97,14 +97,14 @@ TEST_CASE("[OS] Feature tags") { OS::get_singleton()->has_feature("editor"), "The binary has the \"editor\" feature tag."); CHECK_MESSAGE( - !OS::get_singleton()->has_feature("standalone"), - "The binary does not have the \"standalone\" feature tag."); + !OS::get_singleton()->has_feature("template"), + "The binary does not have the \"template\" feature tag."); CHECK_MESSAGE( - OS::get_singleton()->has_feature("debug"), - "The binary has the \"debug\" feature tag."); + !OS::get_singleton()->has_feature("template_debug"), + "The binary does not have the \"template_debug\" feature tag."); CHECK_MESSAGE( - !OS::get_singleton()->has_feature("release"), - "The binary does not have the \"release\" feature tag."); + !OS::get_singleton()->has_feature("template_release"), + "The binary does not have the \"template_release\" feature tag."); } TEST_CASE("[OS] Process ID") { diff --git a/thirdparty/README.md b/thirdparty/README.md index 937d149747..ed38fe0ec8 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -20,7 +20,7 @@ Files extracted from upstream source: ## basis_universal - Upstream: https://github.com/BinomialLLC/basis_universal -- Version: git (1531cfaf9ed5232248a0a45736686a849ca3befc, 2022) +- Version: git (a91e94c8495d7f470d3df326a364d49324cfd4a3, 2022) - License: Apache 2.0 Files extracted from upstream source: diff --git a/thirdparty/basis_universal/encoder/basisu_comp.cpp b/thirdparty/basis_universal/encoder/basisu_comp.cpp index 166a1c4fe0..41eae2b78a 100644 --- a/thirdparty/basis_universal/encoder/basisu_comp.cpp +++ b/thirdparty/basis_universal/encoder/basisu_comp.cpp @@ -1501,7 +1501,8 @@ namespace basisu if (m_params.m_compute_stats) { - printf("Slice: %u\n", slice_index); + if (m_params.m_print_stats) + printf("Slice: %u\n", slice_index); image_stats& s = m_stats[slice_index]; @@ -1511,81 +1512,100 @@ namespace basisu // ---- .basis stats em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 3); - em.print(".basis RGB Avg: "); + if (m_params.m_print_stats) + em.print(".basis RGB Avg: "); s.m_basis_rgb_avg_psnr = em.m_psnr; em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 4); - em.print(".basis RGBA Avg: "); + if (m_params.m_print_stats) + em.print(".basis RGBA Avg: "); s.m_basis_rgba_avg_psnr = em.m_psnr; em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 1); - em.print(".basis R Avg: "); + if (m_params.m_print_stats) + em.print(".basis R Avg: "); em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 1, 1); - em.print(".basis G Avg: "); + if (m_params.m_print_stats) + em.print(".basis G Avg: "); em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 2, 1); - em.print(".basis B Avg: "); + if (m_params.m_print_stats) + em.print(".basis B Avg: "); if (m_params.m_uastc) { em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 3, 1); - em.print(".basis A Avg: "); + if (m_params.m_print_stats) + em.print(".basis A Avg: "); s.m_basis_a_avg_psnr = em.m_psnr; } em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0); - em.print(".basis 709 Luma: "); + if (m_params.m_print_stats) + em.print(".basis 709 Luma: "); s.m_basis_luma_709_psnr = static_cast<float>(em.m_psnr); s.m_basis_luma_709_ssim = static_cast<float>(em.m_ssim); em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked[slice_index], 0, 0, true, true); - em.print(".basis 601 Luma: "); + if (m_params.m_print_stats) + em.print(".basis 601 Luma: "); s.m_basis_luma_601_psnr = static_cast<float>(em.m_psnr); if (m_slice_descs.size() == 1) { const uint32_t output_size = comp_size ? (uint32_t)comp_size : (uint32_t)comp_data.size(); - debug_printf(".basis RGB PSNR per bit/texel*10000: %3.3f\n", 10000.0f * s.m_basis_rgb_avg_psnr / ((output_size * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height))); - debug_printf(".basis Luma 709 PSNR per bit/texel*10000: %3.3f\n", 10000.0f * s.m_basis_luma_709_psnr / ((output_size * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height))); + if (m_params.m_print_stats) + { + debug_printf(".basis RGB PSNR per bit/texel*10000: %3.3f\n", 10000.0f * s.m_basis_rgb_avg_psnr / ((output_size * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height))); + debug_printf(".basis Luma 709 PSNR per bit/texel*10000: %3.3f\n", 10000.0f * s.m_basis_luma_709_psnr / ((output_size * 8.0f) / (slice_desc.m_orig_width * slice_desc.m_orig_height))); + } } if (m_decoded_output_textures_unpacked_bc7[slice_index].get_width()) { // ---- BC7 stats em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 3); - em.print("BC7 RGB Avg: "); + if (m_params.m_print_stats) + em.print("BC7 RGB Avg: "); s.m_bc7_rgb_avg_psnr = em.m_psnr; em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 4); - em.print("BC7 RGBA Avg: "); + if (m_params.m_print_stats) + em.print("BC7 RGBA Avg: "); s.m_bc7_rgba_avg_psnr = em.m_psnr; em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 1); - em.print("BC7 R Avg: "); + if (m_params.m_print_stats) + em.print("BC7 R Avg: "); em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 1, 1); - em.print("BC7 G Avg: "); + if (m_params.m_print_stats) + em.print("BC7 G Avg: "); em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 2, 1); - em.print("BC7 B Avg: "); + if (m_params.m_print_stats) + em.print("BC7 B Avg: "); if (m_params.m_uastc) { em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 3, 1); - em.print("BC7 A Avg: "); + if (m_params.m_print_stats) + em.print("BC7 A Avg: "); s.m_bc7_a_avg_psnr = em.m_psnr; } em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 0); - em.print("BC7 709 Luma: "); + if (m_params.m_print_stats) + em.print("BC7 709 Luma: "); s.m_bc7_luma_709_psnr = static_cast<float>(em.m_psnr); s.m_bc7_luma_709_ssim = static_cast<float>(em.m_ssim); em.calc(m_slice_images[slice_index], m_decoded_output_textures_unpacked_bc7[slice_index], 0, 0, true, true); - em.print("BC7 601 Luma: "); + if (m_params.m_print_stats) + em.print("BC7 601 Luma: "); s.m_bc7_luma_601_psnr = static_cast<float>(em.m_psnr); } @@ -1593,16 +1613,19 @@ namespace basisu { // ---- Nearly best possible ETC1S stats em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 3); - em.print("Unquantized ETC1S RGB Avg: "); + if (m_params.m_print_stats) + em.print("Unquantized ETC1S RGB Avg: "); s.m_best_etc1s_rgb_avg_psnr = static_cast<float>(em.m_psnr); em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0); - em.print("Unquantized ETC1S 709 Luma: "); + if (m_params.m_print_stats) + em.print("Unquantized ETC1S 709 Luma: "); s.m_best_etc1s_luma_709_psnr = static_cast<float>(em.m_psnr); s.m_best_etc1s_luma_709_ssim = static_cast<float>(em.m_ssim); em.calc(m_slice_images[slice_index], m_best_etc1s_images_unpacked[slice_index], 0, 0, true, true); - em.print("Unquantized ETC1S 601 Luma: "); + if (m_params.m_print_stats) + em.print("Unquantized ETC1S 601 Luma: "); s.m_best_etc1s_luma_601_psnr = static_cast<float>(em.m_psnr); } } @@ -2311,6 +2334,8 @@ namespace basisu } comp_params.m_compute_stats = (pStats != nullptr); + comp_params.m_print_stats = (flags_and_quality & cFlagPrintStats) != 0; + comp_params.m_status_output = (flags_and_quality & cFlagPrintStatus) != 0; // Create the compressor, initialize it, and process the input basis_compressor comp; @@ -2328,6 +2353,11 @@ namespace basisu return nullptr; } + if ((pStats) && (comp.get_opencl_failed())) + { + pStats->m_opencl_failed = true; + } + // Get the output file data and return it to the caller void* pFile_data = nullptr; const uint8_vec* pFile_data_vec = comp_params.m_create_ktx2_file ? &comp.get_output_ktx2_file() : &comp.get_output_basis_file(); @@ -2388,4 +2418,108 @@ namespace basisu free(p); } + bool basis_benchmark_etc1s_opencl(bool* pOpenCL_failed) + { + if (pOpenCL_failed) + *pOpenCL_failed = false; + + if (!opencl_is_available()) + { + error_printf("basis_benchmark_etc1s_opencl: OpenCL support must be enabled first!\n"); + return false; + } + + const uint32_t W = 1024, H = 1024; + basisu::vector<image> images; + image& img = images.enlarge(1)->resize(W, H); + + const uint32_t NUM_RAND_LETTERS = 6000;// 40000; + + rand r; + r.seed(200); + + for (uint32_t i = 0; i < NUM_RAND_LETTERS; i++) + { + uint32_t x = r.irand(0, W - 1), y = r.irand(0, H - 1); + uint32_t sx = r.irand(1, 4), sy = r.irand(1, 4); + color_rgba c(r.byte(), r.byte(), r.byte(), 255); + + img.debug_text(x, y, sx, sy, c, nullptr, false, "%c", static_cast<char>(r.irand(32, 127))); + } + + //save_png("test.png", img); + + image_stats stats; + + uint32_t flags_and_quality = cFlagSRGB | cFlagThreaded | 255; + size_t comp_size = 0; + + double best_cpu_time = 1e+9f, best_gpu_time = 1e+9f; + + const uint32_t TIMES_TO_ENCODE = 2; + interval_timer tm; + + for (uint32_t i = 0; i < TIMES_TO_ENCODE; i++) + { + tm.start(); + void* pComp_data = basis_compress( + images, + flags_and_quality, 1.0f, + &comp_size, + &stats); + double cpu_time = tm.get_elapsed_secs(); + if (!pComp_data) + { + error_printf("basis_benchmark_etc1s_opencl: basis_compress() failed (CPU)!\n"); + return false; + } + + best_cpu_time = minimum(best_cpu_time, cpu_time); + + basis_free_data(pComp_data); + } + + printf("Best CPU time: %3.3f\n", best_cpu_time); + + for (uint32_t i = 0; i < TIMES_TO_ENCODE; i++) + { + tm.start(); + void* pComp_data = basis_compress( + images, + flags_and_quality | cFlagUseOpenCL, 1.0f, + &comp_size, + &stats); + + if (stats.m_opencl_failed) + { + error_printf("basis_benchmark_etc1s_opencl: OpenCL failed!\n"); + + basis_free_data(pComp_data); + + if (pOpenCL_failed) + *pOpenCL_failed = true; + + return false; + } + + double gpu_time = tm.get_elapsed_secs(); + if (!pComp_data) + { + error_printf("basis_benchmark_etc1s_opencl: basis_compress() failed (GPU)!\n"); + return false; + } + + best_gpu_time = minimum(best_gpu_time, gpu_time); + + basis_free_data(pComp_data); + } + + printf("Best GPU time: %3.3f\n", best_gpu_time); + + return best_gpu_time < best_cpu_time; + } + } // namespace basisu + + + diff --git a/thirdparty/basis_universal/encoder/basisu_comp.h b/thirdparty/basis_universal/encoder/basisu_comp.h index aa5ea6fec3..b6c9fef9e2 100644 --- a/thirdparty/basis_universal/encoder/basisu_comp.h +++ b/thirdparty/basis_universal/encoder/basisu_comp.h @@ -92,6 +92,8 @@ namespace basisu m_best_etc1s_luma_709_psnr = 0.0f; m_best_etc1s_luma_601_psnr = 0.0f; m_best_etc1s_luma_709_ssim = 0.0f; + + m_opencl_failed = false; } std::string m_filename; @@ -119,6 +121,8 @@ namespace basisu float m_best_etc1s_luma_709_psnr; float m_best_etc1s_luma_601_psnr; float m_best_etc1s_luma_709_ssim; + + bool m_opencl_failed; }; template<bool def> @@ -255,6 +259,7 @@ namespace basisu m_write_output_basis_files.clear(); m_compression_level.clear(); m_compute_stats.clear(); + m_print_stats.clear(); m_check_for_alpha.clear(); m_force_alpha.clear(); m_multithreading.clear(); @@ -373,6 +378,9 @@ namespace basisu // Compute and display image metrics bool_param<false> m_compute_stats; + + // Print stats to stdout, if m_compute_stats is true. + bool_param<true> m_print_stats; // Check to see if any input image has an alpha channel, if so then the output basis file will have alpha channels bool_param<true> m_check_for_alpha; @@ -583,11 +591,16 @@ namespace basisu cFlagYFlip = 1 << 16, // flip source image on Y axis before compression cFlagUASTC = 1 << 17, // use UASTC compression vs. ETC1S - cFlagUASTCRDO = 1 << 18 // use RDO postprocessing when generating UASTC files (must set uastc_rdo_quality to the quality scalar) + cFlagUASTCRDO = 1 << 18, // use RDO postprocessing when generating UASTC files (must set uastc_rdo_quality to the quality scalar) + + cFlagPrintStats = 1 << 19, // print image stats to stdout + cFlagPrintStatus = 1 << 20 // print status to stdout }; // This function accepts an array of source images. // If more than one image is provided, it's assumed the images form a mipmap pyramid and automatic mipmap generation is disabled. + // Returns a pointer to the compressed .basis or .ktx2 file data. *pSize is the size of the compressed data. The returned block must be freed using basis_free_data(). + // basisu_encoder_init() MUST be called first! void* basis_compress( const basisu::vector<image> &source_images, uint32_t flags_and_quality, float uastc_rdo_quality, @@ -604,6 +617,12 @@ namespace basisu // Frees the dynamically allocated file data returned by basis_compress(). void basis_free_data(void* p); + // Runs a short benchmark using synthetic image data to time OpenCL encoding vs. CPU encoding, with multithreading enabled. + // Returns true if opencl is worth using on this system, otherwise false. + // If pOpenCL_failed is not null, it will be set to true if OpenCL encoding failed *on this particular machine/driver/BasisU version* and the encoder falled back to CPU encoding. + // basisu_encoder_init() MUST be called first. If OpenCL support wasn't enabled this always returns false. + bool basis_benchmark_etc1s_opencl(bool *pOpenCL_failed = nullptr); + // Parallel compression API struct parallel_results { diff --git a/thirdparty/basis_universal/encoder/basisu_enc.cpp b/thirdparty/basis_universal/encoder/basisu_enc.cpp index b427215ee3..c431ceaf12 100644 --- a/thirdparty/basis_universal/encoder/basisu_enc.cpp +++ b/thirdparty/basis_universal/encoder/basisu_enc.cpp @@ -187,6 +187,8 @@ namespace basisu opencl_init(opencl_force_serialization); } + interval_timer::init(); // make sure interval_timer globals are initialized from main thread to avoid TSAN reports + g_library_initialized = true; } @@ -227,7 +229,7 @@ namespace basisu { QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(pTicks)); } -#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) #include <sys/time.h> inline void query_counter(timer_ticks* pTicks) { diff --git a/thirdparty/basis_universal/encoder/basisu_frontend.cpp b/thirdparty/basis_universal/encoder/basisu_frontend.cpp index 00210e6679..1f30a33c70 100644 --- a/thirdparty/basis_universal/encoder/basisu_frontend.cpp +++ b/thirdparty/basis_universal/encoder/basisu_frontend.cpp @@ -2328,8 +2328,6 @@ namespace basisu m_optimized_cluster_selectors.resize(total_selector_clusters); - uint32_t total_clusters_processed = 0; - // For each selector codebook entry, and for each of the 4x4 selectors, determine which selector minimizes the error across all the blocks that use that quantized selector. const uint32_t N = 256; for (uint32_t cluster_index_iter = 0; cluster_index_iter < total_selector_clusters; cluster_index_iter += N) @@ -2338,7 +2336,7 @@ namespace basisu const uint32_t last_index = minimum<uint32_t>((uint32_t)total_selector_clusters, cluster_index_iter + N); #ifndef __EMSCRIPTEN__ - m_params.m_pJob_pool->add_job([this, first_index, last_index, &total_clusters_processed, &total_selector_clusters] { + m_params.m_pJob_pool->add_job([this, first_index, last_index] { #endif for (uint32_t cluster_index = first_index; cluster_index < last_index; cluster_index++) diff --git a/thirdparty/basis_universal/transcoder/basisu_transcoder.cpp b/thirdparty/basis_universal/transcoder/basisu_transcoder.cpp index 630731900f..3aeba0ee7a 100644 --- a/thirdparty/basis_universal/transcoder/basisu_transcoder.cpp +++ b/thirdparty/basis_universal/transcoder/basisu_transcoder.cpp @@ -16867,7 +16867,7 @@ namespace basist { m_format = basist::basis_tex_format::cETC1S; - // 3.10.2: "Whether the image has 1 or 2 slices can be determined from the DFD’s sample count." + // 3.10.2: "Whether the image has 1 or 2 slices can be determined from the DFD's sample count." // If m_has_alpha is true it may be 2-channel RRRG or 4-channel RGBA, but we let the caller deal with that. m_has_alpha = (m_header.m_dfd_byte_length == 60); diff --git a/thirdparty/embree/common/sys/sysinfo.cpp b/thirdparty/embree/common/sys/sysinfo.cpp index c98f61fa53..7f7a009a1e 100644 --- a/thirdparty/embree/common/sys/sysinfo.cpp +++ b/thirdparty/embree/common/sys/sysinfo.cpp @@ -640,6 +640,12 @@ namespace embree #if defined(__EMSCRIPTEN__) #include <emscripten.h> + +// -- GODOT start -- +extern "C" { +extern int godot_js_os_hw_concurrency_get(); +} +// -- GODOT end -- #endif namespace embree @@ -653,21 +659,9 @@ namespace embree nThreads = sysconf(_SC_NPROCESSORS_ONLN); // does not work in Linux LXC container assert(nThreads); #elif defined(__EMSCRIPTEN__) - // WebAssembly supports pthreads, but not pthread_getaffinity_np. Get the number of logical - // threads from the browser or Node.js using JavaScript. - nThreads = MAIN_THREAD_EM_ASM_INT({ - const isBrowser = typeof window !== 'undefined'; - const isNode = typeof process !== 'undefined' && process.versions != null && - process.versions.node != null; - if (isBrowser) { - // Return 1 if the browser does not expose hardwareConcurrency. - return window.navigator.hardwareConcurrency || 1; - } else if (isNode) { - return require('os').cpus().length; - } else { - return 1; - } - }); + // -- GODOT start -- + nThreads = godot_js_os_hw_concurrency_get(); + // -- GODOT end -- #else cpu_set_t set; if (pthread_getaffinity_np(pthread_self(), sizeof(set), &set) == 0) diff --git a/thirdparty/embree/patches/emscripten-nthreads.patch b/thirdparty/embree/patches/emscripten-nthreads.patch new file mode 100644 index 0000000000..e42f203475 --- /dev/null +++ b/thirdparty/embree/patches/emscripten-nthreads.patch @@ -0,0 +1,42 @@ +diff --git a/thirdparty/embree/common/sys/sysinfo.cpp b/thirdparty/embree/common/sys/sysinfo.cpp +index c98f61fa53..7f7a009a1e 100644 +--- a/thirdparty/embree/common/sys/sysinfo.cpp ++++ b/thirdparty/embree/common/sys/sysinfo.cpp +@@ -640,6 +640,12 @@ namespace embree + + #if defined(__EMSCRIPTEN__) + #include <emscripten.h> ++ ++// -- GODOT start -- ++extern "C" { ++extern int godot_js_os_hw_concurrency_get(); ++} ++// -- GODOT end -- + #endif + + namespace embree +@@ -653,21 +659,9 @@ namespace embree + nThreads = sysconf(_SC_NPROCESSORS_ONLN); // does not work in Linux LXC container + assert(nThreads); + #elif defined(__EMSCRIPTEN__) +- // WebAssembly supports pthreads, but not pthread_getaffinity_np. Get the number of logical +- // threads from the browser or Node.js using JavaScript. +- nThreads = MAIN_THREAD_EM_ASM_INT({ +- const isBrowser = typeof window !== 'undefined'; +- const isNode = typeof process !== 'undefined' && process.versions != null && +- process.versions.node != null; +- if (isBrowser) { +- // Return 1 if the browser does not expose hardwareConcurrency. +- return window.navigator.hardwareConcurrency || 1; +- } else if (isNode) { +- return require('os').cpus().length; +- } else { +- return 1; +- } +- }); ++ // -- GODOT start -- ++ nThreads = godot_js_os_hw_concurrency_get(); ++ // -- GODOT end -- + #else + cpu_set_t set; + if (pthread_getaffinity_np(pthread_self(), sizeof(set), &set) == 0) |