diff options
69 files changed, 1378 insertions, 255 deletions
diff --git a/core/object/object.cpp b/core/object/object.cpp index 2e5b897bce..40df13849b 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -198,6 +198,7 @@ bool Object::_predelete() { notification(NOTIFICATION_PREDELETE, true); if (_predelete_ok) { _class_name_ptr = nullptr; // Must restore, so constructors/destructors have proper class name access at each stage. + notification(NOTIFICATION_PREDELETE_CLEANUP, true); } return _predelete_ok; } diff --git a/core/object/object.h b/core/object/object.h index a444db0f70..f3c387594b 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -801,6 +801,8 @@ public: NOTIFICATION_POSTINITIALIZE = 0, NOTIFICATION_PREDELETE = 1, NOTIFICATION_EXTENSION_RELOADED = 2, + // Internal notification to send after NOTIFICATION_PREDELETE, not bound to scripting. + NOTIFICATION_PREDELETE_CLEANUP = 3, }; /* TYPE API */ diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index 011f4203ea..2bdbfb5ad1 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -39,10 +39,11 @@ ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES]; int ScriptServer::_language_count = 0; +bool ScriptServer::languages_ready = false; +Mutex ScriptServer::languages_mutex; bool ScriptServer::scripting_enabled = true; bool ScriptServer::reload_scripts_on_save = false; -SafeFlag ScriptServer::languages_finished; // Used until GH-76581 is fixed properly. ScriptEditRequestFunction ScriptServer::edit_request_func = nullptr; void Script::_notification(int p_what) { @@ -160,12 +161,13 @@ bool ScriptServer::is_scripting_enabled() { } ScriptLanguage *ScriptServer::get_language(int p_idx) { + MutexLock lock(languages_mutex); ERR_FAIL_INDEX_V(p_idx, _language_count, nullptr); - return _languages[p_idx]; } Error ScriptServer::register_language(ScriptLanguage *p_language) { + MutexLock lock(languages_mutex); ERR_FAIL_NULL_V(p_language, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V_MSG(_language_count >= MAX_LANGUAGES, ERR_UNAVAILABLE, "Script languages limit has been reach, cannot register more."); for (int i = 0; i < _language_count; i++) { @@ -179,6 +181,8 @@ Error ScriptServer::register_language(ScriptLanguage *p_language) { } Error ScriptServer::unregister_language(const ScriptLanguage *p_language) { + MutexLock lock(languages_mutex); + for (int i = 0; i < _language_count; i++) { if (_languages[i] == p_language) { _language_count--; @@ -219,17 +223,31 @@ void ScriptServer::init_languages() { } } - for (int i = 0; i < _language_count; i++) { - _languages[i]->init(); + { + MutexLock lock(languages_mutex); + + for (int i = 0; i < _language_count; i++) { + _languages[i]->init(); + } + + languages_ready = true; } } void ScriptServer::finish_languages() { + MutexLock lock(languages_mutex); + for (int i = 0; i < _language_count; i++) { _languages[i]->finish(); } global_classes_clear(); - languages_finished.set(); + + languages_ready = false; +} + +bool ScriptServer::are_languages_initialized() { + MutexLock lock(languages_mutex); + return languages_ready; } void ScriptServer::set_reload_scripts_on_save(bool p_enable) { @@ -241,7 +259,8 @@ bool ScriptServer::is_reload_scripts_on_save_enabled() { } void ScriptServer::thread_enter() { - if (!languages_finished.is_set()) { + MutexLock lock(languages_mutex); + if (!languages_ready) { return; } for (int i = 0; i < _language_count; i++) { @@ -250,7 +269,8 @@ void ScriptServer::thread_enter() { } void ScriptServer::thread_exit() { - if (!languages_finished.is_set()) { + MutexLock lock(languages_mutex); + if (!languages_ready) { return; } for (int i = 0; i < _language_count; i++) { diff --git a/core/object/script_language.h b/core/object/script_language.h index 3e4041d173..85e64c8d62 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -52,9 +52,11 @@ class ScriptServer { static ScriptLanguage *_languages[MAX_LANGUAGES]; static int _language_count; + static bool languages_ready; + static Mutex languages_mutex; + static bool scripting_enabled; static bool reload_scripts_on_save; - static SafeFlag languages_finished; // Used until GH-76581 is fixed properly. struct GlobalScriptClass { StringName language; @@ -98,8 +100,7 @@ public: static void init_languages(); static void finish_languages(); - - static bool are_languages_finished() { return languages_finished.is_set(); } + static bool are_languages_initialized(); }; class PlaceHolderScriptInstance; diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index 2fcd0867e6..784acadab4 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -30,6 +30,7 @@ #include "worker_thread_pool.h" +#include "core/object/script_language.h" #include "core/os/os.h" #include "core/os/thread_safe.h" @@ -60,6 +61,14 @@ void WorkerThreadPool::_process_task(Task *p_task) { set_current_thread_safe_for_nodes(false); pool_thread_index = thread_ids[Thread::get_caller_id()]; ThreadData &curr_thread = threads[pool_thread_index]; + // Since the WorkerThreadPool is started before the script server, + // its pre-created threads can't have ScriptServer::thread_enter() called on them early. + // Therefore, we do it late at the first opportunity, so in case the task + // about to be run uses scripting, guarantees are held. + if (!curr_thread.ready_for_scripting && ScriptServer::are_languages_initialized()) { + ScriptServer::thread_enter(); + curr_thread.ready_for_scripting = true; + } task_mutex.lock(); p_task->pool_thread_index = pool_thread_index; if (low_priority) { diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index d4d9387765..f323a979f7 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -106,6 +106,7 @@ private: uint32_t index; Thread thread; Task *current_low_prio_task = nullptr; + bool ready_for_scripting = false; }; TightLocalVector<ThreadData> threads; diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 5a8df07410..658297d805 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -122,7 +122,7 @@ void StringName::unref() { if (_data && _data->refcount.unref()) { MutexLock lock(mutex); - if (_data->static_count.get() > 0) { + if (CoreGlobals::leak_reporting_enabled && _data->static_count.get() > 0) { if (_data->cname) { ERR_PRINT("BUG: Unreferenced static string to 0: " + String(_data->cname)); } else { diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 9be7c04158..60e2d539f8 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -4699,11 +4699,16 @@ String String::property_name_encode() const { static const char32_t invalid_node_name_characters[] = { '.', ':', '@', '/', '\"', UNIQUE_NODE_PREFIX[0], 0 }; -String String::get_invalid_node_name_characters() { +String String::get_invalid_node_name_characters(bool p_allow_internal) { // Do not use this function for critical validation. String r; const char32_t *c = invalid_node_name_characters; while (*c) { + if (p_allow_internal && *c == '@') { + c++; + continue; + } + if (c != invalid_node_name_characters) { r += " "; } diff --git a/core/string/ustring.h b/core/string/ustring.h index f45392eee1..897b06fc6d 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -437,7 +437,7 @@ public: String property_name_encode() const; // node functions - static String get_invalid_node_name_characters(); + static String get_invalid_node_name_characters(bool p_allow_internal = false); String validate_node_name() const; String validate_identifier() const; String validate_filename() const; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 09fb34e7c1..4c0212075b 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -1291,7 +1291,13 @@ void Variant::zero() { break; default: + Type prev_type = type; this->clear(); + if (type != prev_type) { + // clear() changes type to NIL, so it needs to be restored. + Callable::CallError ce; + Variant::construct(prev_type, *this, nullptr, 0, ce); + } break; } } diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 84efd11b43..5a2df0e8a4 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -44,7 +44,7 @@ <param index="7" name="antialiased" type="bool" default="false" /> <description> Draws an unfilled arc between the given angles with a uniform [param color] and [param width] and optional antialiasing (supported only for positive [param width]). The larger the value of [param point_count], the smoother the curve. See also [method draw_circle]. - If [param width] is negative, then the arc is drawn using [constant RenderingServer.PRIMITIVE_LINE_STRIP]. This means that when the CanvasItem is scaled, the arc will remain thin. If this behavior is not desired, then pass a positive [param width] like [code]1.0[/code]. + If [param width] is negative, it will be ignored and the arc will be drawn using [constant RenderingServer.PRIMITIVE_LINE_STRIP]. This means that when the CanvasItem is scaled, the arc will remain thin. If this behavior is not desired, then pass a positive [param width] like [code]1.0[/code]. The arc is drawn from [param start_angle] towards the value of [param end_angle] so in clockwise direction if [code]start_angle < end_angle[/code] and counter-clockwise otherwise. Passing the same angles but in reversed order will produce the same arc. If absolute difference of [param start_angle] and [param end_angle] is greater than [constant @GDScript.TAU] radians, then a full circle arc is drawn (i.e. arc will not overlap itself). </description> </method> @@ -246,7 +246,7 @@ <param index="3" name="antialiased" type="bool" default="false" /> <description> Draws interconnected line segments with a uniform [param color] and [param width] and optional antialiasing (supported only for positive [param width]). When drawing large amounts of lines, this is faster than using individual [method draw_line] calls. To draw disconnected lines, use [method draw_multiline] instead. See also [method draw_polygon]. - If [param width] is negative, the polyline is drawn using [constant RenderingServer.PRIMITIVE_LINE_STRIP]. This means that when the CanvasItem is scaled, the polyline will remain thin. If this behavior is not desired, then pass a positive [param width] like [code]1.0[/code]. + If [param width] is negative, it will be ignored and the polyline will be drawn using [constant RenderingServer.PRIMITIVE_LINE_STRIP]. This means that when the CanvasItem is scaled, the polyline will remain thin. If this behavior is not desired, then pass a positive [param width] like [code]1.0[/code]. </description> </method> <method name="draw_polyline_colors"> @@ -257,7 +257,7 @@ <param index="3" name="antialiased" type="bool" default="false" /> <description> Draws interconnected line segments with a uniform [param width], point-by-point coloring, and optional antialiasing (supported only for positive [param width]). Colors assigned to line points match by index between [param points] and [param colors], i.e. each line segment is filled with a gradient between the colors of the endpoints. When drawing large amounts of lines, this is faster than using individual [method draw_line] calls. To draw disconnected lines, use [method draw_multiline_colors] instead. See also [method draw_polygon]. - If [param width] is negative, then the polyline is drawn using [constant RenderingServer.PRIMITIVE_LINE_STRIP]. This means that when the CanvasItem is scaled, the polyline will remain thin. If this behavior is not desired, then pass a positive [param width] like [code]1.0[/code]. + If [param width] is negative, it will be ignored and the polyline will be drawn using [constant RenderingServer.PRIMITIVE_LINE_STRIP]. This means that when the CanvasItem is scaled, the polyline will remain thin. If this behavior is not desired, then pass a positive [param width] like [code]1.0[/code]. </description> </method> <method name="draw_primitive"> diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index b5333a045b..a498bbeed3 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -1104,13 +1104,13 @@ </signal> <signal name="mouse_entered"> <description> - Emitted when the mouse cursor enters the control's visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not. + Emitted when the mouse cursor enters the control's (or any child control's) visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not. [b]Note:[/b] [member CanvasItem.z_index] doesn't affect, which Control receives the signal. </description> </signal> <signal name="mouse_exited"> <description> - Emitted when the mouse cursor leaves the control's visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not. + Emitted when the mouse cursor leaves the control's (and all child control's) visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not. [b]Note:[/b] [member CanvasItem.z_index] doesn't affect, which Control receives the signal. [b]Note:[/b] If you want to check whether the mouse truly left the area, ignoring any top nodes, you can use code like this: [codeblock] @@ -1150,12 +1150,24 @@ Sent when the node changes size. Use [member size] to get the new size. </constant> <constant name="NOTIFICATION_MOUSE_ENTER" value="41"> - Sent when the mouse cursor enters the control's visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not. - [b]Note:[/b] [member CanvasItem.z_index] doesn't affect, which Control receives the notification. + Sent when the mouse cursor enters the control's (or any child control's) visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not. + [b]Note:[/b] [member CanvasItem.z_index] doesn't affect which Control receives the notification. + See also [constant NOTIFICATION_MOUSE_ENTER_SELF]. </constant> <constant name="NOTIFICATION_MOUSE_EXIT" value="42"> + Sent when the mouse cursor leaves the control's (and all child control's) visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not. + [b]Note:[/b] [member CanvasItem.z_index] doesn't affect which Control receives the notification. + See also [constant NOTIFICATION_MOUSE_EXIT_SELF]. + </constant> + <constant name="NOTIFICATION_MOUSE_ENTER_SELF" value="60" is_experimental="true"> + Sent when the mouse cursor enters the control's visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not. + [b]Note:[/b] [member CanvasItem.z_index] doesn't affect which Control receives the notification. + See also [constant NOTIFICATION_MOUSE_ENTER]. + </constant> + <constant name="NOTIFICATION_MOUSE_EXIT_SELF" value="61" is_experimental="true"> Sent when the mouse cursor leaves the control's visible area, that is not occluded behind other Controls or Windows, provided its [member mouse_filter] lets the event reach it and regardless if it's currently focused or not. - [b]Note:[/b] [member CanvasItem.z_index] doesn't affect, which Control receives the notification. + [b]Note:[/b] [member CanvasItem.z_index] doesn't affect which Control receives the notification. + See also [constant NOTIFICATION_MOUSE_EXIT]. </constant> <constant name="NOTIFICATION_FOCUS_ENTER" value="43"> Sent when the node grabs focus. @@ -1320,6 +1332,7 @@ </constant> <constant name="MOUSE_FILTER_IGNORE" value="2" enum="MouseFilter"> The control will not receive mouse movement input events and mouse button input events if clicked on through [method _gui_input]. The control will also not receive the [signal mouse_entered] nor [signal mouse_exited] signals. This will not block other controls from receiving these events or firing the signals. Ignored events will not be handled automatically. + [b]Note:[/b] If the control has received [signal mouse_entered] but not [signal mouse_exited], changing the [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] will cause [signal mouse_exited] to be emitted. </constant> <constant name="GROW_DIRECTION_BEGIN" value="0" enum="GrowDirection"> The control will grow to the left or top to make up if its minimum size is changed to be greater than its current size on the respective axis. diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 6f858942aa..50709f9ef5 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -541,7 +541,7 @@ <param index="0" name="name" type="String" /> <param index="1" name="submenu" type="PopupMenu" /> <description> - Adds a custom [PopupMenu] submenu under [b]Project > Tools >[/b] [param name]. Use [code]remove_tool_menu_item(name)[/code] on plugin clean up to remove the menu. + Adds a custom [PopupMenu] submenu under [b]Project > Tools >[/b] [param name]. Use [method remove_tool_menu_item] on plugin clean up to remove the menu. </description> </method> <method name="add_translation_parser_plugin"> diff --git a/doc/classes/EditorSpinSlider.xml b/doc/classes/EditorSpinSlider.xml index 6e5ccb4dd0..0d687ba7f5 100644 --- a/doc/classes/EditorSpinSlider.xml +++ b/doc/classes/EditorSpinSlider.xml @@ -5,6 +5,7 @@ </brief_description> <description> This [Control] node is used in the editor's Inspector dock to allow editing of numeric values. Can be used with [EditorInspectorPlugin] to recreate the same behavior. + If [member step] is [code]1[/code], the [EditorSpinSlider] will display up/down arrows, similar to [SpinBox]. If the [member step] is not [code]1[/code], a slider will be displayed instead. </description> <tutorials> </tutorials> @@ -14,7 +15,7 @@ </member> <member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="2" /> <member name="hide_slider" type="bool" setter="set_hide_slider" getter="is_hiding_slider" default="false"> - If [code]true[/code], the slider is hidden. + If [code]true[/code], the slider and up/down arrows are hidden. </member> <member name="label" type="String" setter="set_label" getter="get_label" default=""""> The text that displays to the left of the value. diff --git a/doc/classes/InputEventKey.xml b/doc/classes/InputEventKey.xml index 5c4dc8e65d..48a6804290 100644 --- a/doc/classes/InputEventKey.xml +++ b/doc/classes/InputEventKey.xml @@ -79,7 +79,25 @@ </member> <member name="physical_keycode" type="int" setter="set_physical_keycode" getter="get_physical_keycode" enum="Key" default="0"> Represents the physical location of a key on the 101/102-key US QWERTY keyboard, which corresponds to one of the [enum Key] constants. - To get a human-readable representation of the [InputEventKey], use [code]OS.get_keycode_string(event.keycode)[/code] where [code]event[/code] is the [InputEventKey]. + To get a human-readable representation of the [InputEventKey], use [method OS.get_keycode_string] in combination with [method DisplayServer.keyboard_get_keycode_from_physical]: + [codeblocks] + [gdscript] + func _input(event): + if event is InputEventKey: + var keycode = DisplayServer.keyboard_get_keycode_from_physical(event.physical_keycode) + print(OS.get_keycode_string(keycode)) + [/gdscript] + [csharp] + public override void _Input(InputEvent @event) + { + if (@event is InputEventKey inputEventKey) + { + var keycode = DisplayServer.KeyboardGetKeycodeFromPhysical(inputEventKey.PhysicalKeycode); + GD.Print(OS.GetKeycodeString(keycode)); + } + } + [/csharp] + [/codeblocks] </member> <member name="pressed" type="bool" setter="set_pressed" getter="is_pressed" default="false"> If [code]true[/code], the key's state is pressed. If [code]false[/code], the key's state is released. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index d8efdb455b..93e0ed5491 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -349,7 +349,7 @@ If [member display/window/vsync/vsync_mode] is [code]Enabled[/code], on monitors with variable refresh rate enabled (G-Sync/FreeSync), using a FPS limit a few frames lower than the monitor's refresh rate will [url=https://blurbusters.com/howto-low-lag-vsync-on/]reduce input lag while avoiding tearing[/url]. If [member display/window/vsync/vsync_mode] is [code]Disabled[/code], limiting the FPS to a high value that can be consistently reached on the system can reduce input lag compared to an uncapped framerate. Since this works by ensuring the GPU load is lower than 100%, this latency reduction is only effective in GPU-bottlenecked scenarios, not CPU-bottlenecked scenarios. See also [member physics/common/physics_ticks_per_second]. - This setting can be overridden using the [code]--max-fps <fps;>[/code] command line argument (including with a value of [code]0[/code] for unlimited framerate). + This setting can be overridden using the [code]--max-fps <fps>[/code] command line argument (including with a value of [code]0[/code] for unlimited framerate). [b]Note:[/b] This property is only read when the project starts. To change the rendering FPS cap at runtime, set [member Engine.max_fps] instead. </member> <member name="audio/buses/channel_disable_threshold_db" type="float" setter="" getter="" default="-60.0"> @@ -2393,11 +2393,15 @@ If [code]true[/code], the compatibility renderer will fall back to ANGLE if native OpenGL is not supported or the device is listed in [member rendering/gl_compatibility/force_angle_on_devices]. [b]Note:[/b] This setting is implemented only on Windows. </member> + <member name="rendering/gl_compatibility/fallback_to_gles" type="bool" setter="" getter="" default="true"> + If [code]true[/code], the compatibility renderer will fall back to OpenGLES if desktop OpenGL is not supported. + [b]Note:[/b] This setting is implemented only on Linux/X11. + </member> <member name="rendering/gl_compatibility/fallback_to_native" type="bool" setter="" getter="" default="true"> If [code]true[/code], the compatibility renderer will fall back to native OpenGL if ANGLE over Metal is not supported. [b]Note:[/b] This setting is implemented only on macOS. </member> - <member name="rendering/gl_compatibility/force_angle_on_devices" type="Array" setter="" getter="" default="[]"> + <member name="rendering/gl_compatibility/force_angle_on_devices" type="Array" setter="" getter=""> An [Array] of devices which should always use the ANGLE renderer. Each entry is a [Dictionary] with the following keys: [code]vendor[/code] and [code]name[/code]. [code]name[/code] can be set to [code]*[/code] to add all devices with the specified [code]vendor[/code]. [b]Note:[/b] This setting is implemented only on Windows. @@ -2694,7 +2698,6 @@ Affects the final texture sharpness by reading from a lower or higher mipmap (also called "texture LOD bias"). Negative values make mipmapped textures sharper but grainier when viewed at a distance, while positive values make mipmapped textures blurrier (even when up close). Enabling temporal antialiasing ([member rendering/anti_aliasing/quality/use_taa]) will automatically apply a [code]-0.5[/code] offset to this value, while enabling FXAA ([member rendering/anti_aliasing/quality/screen_space_aa]) will automatically apply a [code]-0.25[/code] offset to this value. If both TAA and FXAA are enabled at the same time, an offset of [code]-0.75[/code] is applied to this value. [b]Note:[/b] If [member rendering/scaling_3d/scale] is lower than [code]1.0[/code] (exclusive), [member rendering/textures/default_filters/texture_mipmap_bias] is used to adjust the automatic mipmap bias which is calculated internally based on the scale factor. The formula for this is [code]log2(scaling_3d_scale) + mipmap_bias[/code]. - [b]Note:[/b] This property is only read when the project starts. To change the mipmap LOD bias at run-time, set [member Viewport.texture_mipmap_bias] instead. </member> <member name="rendering/textures/default_filters/use_nearest_mipmap_filter" type="bool" setter="" getter="" default="false"> If [code]true[/code], uses nearest-neighbor mipmap filtering when using mipmaps (also called "bilinear filtering"), which will result in visible seams appearing between mipmap stages. This may increase performance in mobile as less memory bandwidth is used. If [code]false[/code], linear mipmap filtering (also called "trilinear filtering") is used. diff --git a/doc/classes/ResourceSaver.xml b/doc/classes/ResourceSaver.xml index 7b90781fc5..42c9bd7a3c 100644 --- a/doc/classes/ResourceSaver.xml +++ b/doc/classes/ResourceSaver.xml @@ -42,6 +42,7 @@ Saves a resource to disk to the given path, using a [ResourceFormatSaver] that recognizes the resource object. If [param path] is empty, [ResourceSaver] will try to use [member Resource.resource_path]. The [param flags] bitmask can be specified to customize the save behavior using [enum SaverFlags] flags. Returns [constant OK] on success. + [b]Note:[/b] When the project is running, any generated UID associated with the resource will not be saved as the required code is only executed in editor mode. </description> </method> </methods> diff --git a/doc/classes/ShapeCast2D.xml b/doc/classes/ShapeCast2D.xml index a04ffe3881..d71c9ce13a 100644 --- a/doc/classes/ShapeCast2D.xml +++ b/doc/classes/ShapeCast2D.xml @@ -34,7 +34,7 @@ <method name="force_shapecast_update"> <return type="void" /> <description> - Updates the collision information for the shape. Use this method to update the collision information immediately instead of waiting for the next [code]_physics_process[/code] call, for example if the shape or its parent has changed state. + Updates the collision information for the shape immediately, without waiting for the next [code]_physics_process[/code] call. Use this method, for example, when the shape or its parent has changed state. [b]Note:[/b] [code]enabled == true[/code] is not required for this to work. </description> </method> @@ -130,10 +130,10 @@ </methods> <members> <member name="collide_with_areas" type="bool" setter="set_collide_with_areas" getter="is_collide_with_areas_enabled" default="false"> - If [code]true[/code], collision with [Area2D]s will be reported. + If [code]true[/code], collisions with [Area2D]s will be reported. </member> <member name="collide_with_bodies" type="bool" setter="set_collide_with_bodies" getter="is_collide_with_bodies_enabled" default="true"> - If [code]true[/code], collision with [PhysicsBody2D]s will be reported. + If [code]true[/code], collisions with [PhysicsBody2D]s will be reported. </member> <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1"> The shape's collision mask. Only objects in at least one collision layer enabled in the mask will be detected. diff --git a/doc/classes/ShapeCast3D.xml b/doc/classes/ShapeCast3D.xml index ce72944098..e189628f4c 100644 --- a/doc/classes/ShapeCast3D.xml +++ b/doc/classes/ShapeCast3D.xml @@ -34,7 +34,7 @@ <method name="force_shapecast_update"> <return type="void" /> <description> - Updates the collision information for the shape. Use this method to update the collision information immediately instead of waiting for the next [code]_physics_process[/code] call, for example if the shape or its parent has changed state. + Updates the collision information for the shape immediately, without waiting for the next [code]_physics_process[/code] call. Use this method, for example, when the shape or its parent has changed state. [b]Note:[/b] [code]enabled == true[/code] is not required for this to work. </description> </method> @@ -137,10 +137,10 @@ </methods> <members> <member name="collide_with_areas" type="bool" setter="set_collide_with_areas" getter="is_collide_with_areas_enabled" default="false"> - If [code]true[/code], collision with [Area3D]s will be reported. + If [code]true[/code], collisions with [Area3D]s will be reported. </member> <member name="collide_with_bodies" type="bool" setter="set_collide_with_bodies" getter="is_collide_with_bodies_enabled" default="true"> - If [code]true[/code], collision with [PhysicsBody3D]s will be reported. + If [code]true[/code], collisions with [PhysicsBody3D]s will be reported. </member> <member name="collision_mask" type="int" setter="set_collision_mask" getter="get_collision_mask" default="1"> The shape's collision mask. Only objects in at least one collision layer enabled in the mask will be detected. See [url=$DOCS_URL/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information. diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index e62b3d56d7..6db11e4b29 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -1224,7 +1224,11 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Create method signature and anchor point. if i == 0: - f.write(f".. _class_{class_name}_method_{m.name}:\n\n") + method_qualifier = "" + if m.name.startswith("_"): + method_qualifier = "private_" + + f.write(f".. _class_{class_name}_{method_qualifier}method_{m.name}:\n\n") f.write(".. rst-class:: classref-method\n\n") @@ -1388,6 +1392,11 @@ def make_method_signature( for parameter in definition.parameters: out += f"_{parameter.type_name.type_name}" out += f">` " + elif ref_type == "method": + ref_type_qualifier = "" + if definition.name.startswith("_"): + ref_type_qualifier = "private_" + out += f":ref:`{definition.name}<class_{class_def.name}_{ref_type_qualifier}{ref_type}_{definition.name}>` " else: out += f":ref:`{definition.name}<class_{class_def.name}_{ref_type}_{definition.name}>` " else: @@ -1915,19 +1924,21 @@ def format_text_block( ) # Default to the tag command name. This works by default for most tags, - # but member and theme_item have special cases. + # but method, member, and theme_item have special cases. ref_type = "_{}".format(tag_state.name) - if tag_state.name == "member": - ref_type = "_property" if target_class_name in state.classes: class_def = state.classes[target_class_name] - if tag_state.name == "method" and target_name not in class_def.methods: - print_error( - f'{state.current_class}.xml: Unresolved method reference "{link_target}" in {context_name}.', - state, - ) + if tag_state.name == "method": + if target_name.startswith("_"): + ref_type = "_private_method" + + if target_name not in class_def.methods: + print_error( + f'{state.current_class}.xml: Unresolved method reference "{link_target}" in {context_name}.', + state, + ) elif tag_state.name == "constructor" and target_name not in class_def.constructors: print_error( @@ -1941,11 +1952,14 @@ def format_text_block( state, ) - elif tag_state.name == "member" and target_name not in class_def.properties: - print_error( - f'{state.current_class}.xml: Unresolved member reference "{link_target}" in {context_name}.', - state, - ) + elif tag_state.name == "member": + ref_type = "_property" + + if target_name not in class_def.properties: + print_error( + f'{state.current_class}.xml: Unresolved member reference "{link_target}" in {context_name}.', + state, + ) elif tag_state.name == "signal" and target_name not in class_def.signals: print_error( diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index a3ed75c703..a0dc5f7656 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -1157,8 +1157,8 @@ MaterialStorage::MaterialStorage() { actions.renames["SCREEN_PIXEL_SIZE"] = "screen_pixel_size"; actions.renames["FRAGCOORD"] = "gl_FragCoord"; actions.renames["POINT_COORD"] = "gl_PointCoord"; - actions.renames["INSTANCE_ID"] = "gl_InstanceIndex"; - actions.renames["VERTEX_ID"] = "gl_VertexIndex"; + actions.renames["INSTANCE_ID"] = "gl_InstanceID"; + actions.renames["VERTEX_ID"] = "gl_VertexID"; actions.renames["LIGHT_POSITION"] = "light_position"; actions.renames["LIGHT_DIRECTION"] = "light_direction"; diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index 6658669d66..be05bfea68 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -339,8 +339,7 @@ void EditorAutoloadSettings::_autoload_button_pressed(Object *p_item, int p_colu undo_redo->add_do_property(ProjectSettings::get_singleton(), name, Variant()); undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, GLOBAL_GET(name)); - undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_persisting", name, true); - undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", order); + undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", name, order); undo_redo->add_do_method(this, "update_autoload"); undo_redo->add_undo_method(this, "update_autoload"); @@ -796,8 +795,7 @@ void EditorAutoloadSettings::autoload_remove(const String &p_name) { undo_redo->add_do_property(ProjectSettings::get_singleton(), name, Variant()); undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, GLOBAL_GET(name)); - undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_persisting", name, true); - undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", order); + undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", name, order); undo_redo->add_do_method(this, "update_autoload"); undo_redo->add_undo_method(this, "update_autoload"); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index b1d591d0f3..b6d1b92f9f 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -261,7 +261,8 @@ void EditorFileSystem::_scan_filesystem() { cpath = name; } else { - Vector<String> split = l.split("::"); + // The last section (deps) may contain the same splitter, so limit the maxsplit to 8 to get the complete deps. + Vector<String> split = l.split("::", true, 8); ERR_CONTINUE(split.size() < 9); String name = split[0]; String file; diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 7884005ab7..b37710724e 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -3750,7 +3750,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ case Variant::VECTOR2I: { EditorPropertyVector2i *editor = memnew(EditorPropertyVector2i(p_wide)); EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1, true); - editor->setup(hint.min, hint.max, 1, true, p_hint == PROPERTY_HINT_LINK, hint.suffix); + editor->setup(hint.min, hint.max, 1, false, p_hint == PROPERTY_HINT_LINK, hint.suffix); return editor; } break; @@ -3777,7 +3777,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ case Variant::VECTOR3I: { EditorPropertyVector3i *editor = memnew(EditorPropertyVector3i(p_wide)); EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1, true); - editor->setup(hint.min, hint.max, 1, true, p_hint == PROPERTY_HINT_LINK, hint.suffix); + editor->setup(hint.min, hint.max, 1, false, p_hint == PROPERTY_HINT_LINK, hint.suffix); return editor; } break; @@ -3791,7 +3791,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ case Variant::VECTOR4I: { EditorPropertyVector4i *editor = memnew(EditorPropertyVector4i); EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1, true); - editor->setup(hint.min, hint.max, 1, true, p_hint == PROPERTY_HINT_LINK, hint.suffix); + editor->setup(hint.min, hint.max, 1, false, p_hint == PROPERTY_HINT_LINK, hint.suffix); return editor; } break; diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index 73375e8093..ed1df4b07f 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -80,7 +80,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); classes.sort_custom<StringName::AlphCompare>(); - menu->add_submenu_item(TTR("Add Animation"), "animations"); + menu->add_submenu_item(TTR("Add Animation"), "AddAnimations"); List<StringName> names; tree->get_animation_list(&names); @@ -796,7 +796,6 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { error_label = memnew(Label); error_panel->add_child(error_label); - error_label->set_text("hmmm"); menu = memnew(PopupMenu); add_child(menu); @@ -804,7 +803,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { animations_menu = memnew(PopupMenu); menu->add_child(animations_menu); - animations_menu->set_name("animations"); + animations_menu->set_name("AddAnimations"); animations_menu->connect("index_pressed", callable_mp(this, &AnimationNodeBlendSpace1DEditor::_add_animation_type)); open_file = memnew(EditorFileDialog); diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index 0645d51485..057170098d 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -125,7 +125,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven classes.sort_custom<StringName::AlphCompare>(); ClassDB::get_inheriters_from_class("AnimationRootNode", &classes); - menu->add_submenu_item(TTR("Add Animation"), "animations"); + menu->add_submenu_item(TTR("Add Animation"), "AddAnimations"); List<StringName> names; tree->get_animation_list(&names); @@ -1073,7 +1073,6 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { add_child(error_panel); error_label = memnew(Label); error_panel->add_child(error_label); - error_label->set_text("eh"); set_custom_minimum_size(Size2(0, 300 * EDSCALE)); @@ -1083,7 +1082,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { animations_menu = memnew(PopupMenu); menu->add_child(animations_menu); - animations_menu->set_name("animations"); + animations_menu->set_name("AddAnimations"); animations_menu->connect("index_pressed", callable_mp(this, &AnimationNodeBlendSpace2DEditor::_add_animation_type)); open_file = memnew(EditorFileDialog); diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index 746a901a0a..11b5cd488b 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -563,7 +563,7 @@ void AnimationNodeStateMachineEditor::_open_menu(const Vector2 &p_position) { List<StringName> animation_names; tree->get_animation_list(&animation_names); - menu->add_submenu_item(TTR("Add Animation"), "animations"); + menu->add_submenu_item(TTR("Add Animation"), "AddAnimations"); if (animation_names.is_empty()) { menu->set_item_disabled(menu->get_item_idx_from_text(TTR("Add Animation")), true); } else { @@ -1774,7 +1774,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { animations_menu = memnew(PopupMenu); menu->add_child(animations_menu); - animations_menu->set_name("animations"); + animations_menu->set_name("AddAnimations"); animations_menu->connect("index_pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_add_animation_type)); connect_menu = memnew(PopupMenu); diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index b636ec4f5c..41eae444f7 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -95,12 +95,12 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) { // Multi-instance, start/stop instances_menu = memnew(PopupMenu); - instances_menu->set_name("run_instances"); + instances_menu->set_name("RunInstances"); instances_menu->set_hide_on_checkable_item_selection(false); debug_menu->add_child(instances_menu); debug_menu->add_separator(); - debug_menu->add_submenu_item(TTR("Run Multiple Instances"), "run_instances"); + debug_menu->add_submenu_item(TTR("Run Multiple Instances"), "RunInstances"); for (int i = 1; i <= 4; i++) { instances_menu->add_radio_check_item(vformat(TTRN("Run %d Instance", "Run %d Instances", i), i)); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index d465b9f27a..8ab35d150e 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -5120,8 +5120,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p display_submenu->add_radio_check_item(TTR("Motion Vectors"), VIEW_DISPLAY_MOTION_VECTORS); display_submenu->add_radio_check_item(TTR("Internal Buffer"), VIEW_DISPLAY_INTERNAL_BUFFER); - display_submenu->set_name("display_advanced"); - view_menu->get_popup()->add_submenu_item(TTR("Display Advanced..."), "display_advanced", VIEW_DISPLAY_ADVANCED); + display_submenu->set_name("DisplayAdvanced"); + view_menu->get_popup()->add_submenu_item(TTR("Display Advanced..."), "DisplayAdvanced", VIEW_DISPLAY_ADVANCED); view_menu->get_popup()->add_separator(); view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_environment", TTR("View Environment")), VIEW_ENVIRONMENT); view_menu->get_popup()->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_gizmos", TTR("View Gizmos")), VIEW_GIZMOS); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 5b67c6d509..9ed997dca7 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -2238,7 +2238,7 @@ void ScriptTextEditor::_enable_code_editor() { edit_menu->get_popup()->add_separator(); { PopupMenu *sub_menu = memnew(PopupMenu); - sub_menu->set_name("line_menu"); + sub_menu->set_name("LineMenu"); sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_up"), EDIT_MOVE_LINE_UP); sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/move_down"), EDIT_MOVE_LINE_DOWN); sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent"), EDIT_INDENT); @@ -2247,46 +2247,46 @@ void ScriptTextEditor::_enable_code_editor() { sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_menu->get_popup()->add_child(sub_menu); - edit_menu->get_popup()->add_submenu_item(TTR("Line"), "line_menu"); + edit_menu->get_popup()->add_submenu_item(TTR("Line"), "LineMenu"); } { PopupMenu *sub_menu = memnew(PopupMenu); - sub_menu->set_name("folding_menu"); + sub_menu->set_name("FoldingMenu"); sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_fold_line"), EDIT_TOGGLE_FOLD_LINE); sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES); sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES); sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/create_code_region"), EDIT_CREATE_CODE_REGION); sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_menu->get_popup()->add_child(sub_menu); - edit_menu->get_popup()->add_submenu_item(TTR("Folding"), "folding_menu"); + edit_menu->get_popup()->add_submenu_item(TTR("Folding"), "FoldingMenu"); } edit_menu->get_popup()->add_separator(); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE); { PopupMenu *sub_menu = memnew(PopupMenu); - sub_menu->set_name("indent_menu"); + sub_menu->set_name("IndentMenu"); sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES); sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS); sub_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/auto_indent"), EDIT_AUTO_INDENT); sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_menu->get_popup()->add_child(sub_menu); - edit_menu->get_popup()->add_submenu_item(TTR("Indentation"), "indent_menu"); + edit_menu->get_popup()->add_submenu_item(TTR("Indentation"), "IndentMenu"); } edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_menu->get_popup()->add_separator(); { PopupMenu *sub_menu = memnew(PopupMenu); - sub_menu->set_name("convert_case"); + sub_menu->set_name("ConvertCase"); sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KeyModifierMask::SHIFT | Key::F4), EDIT_TO_UPPERCASE); sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KeyModifierMask::SHIFT | Key::F5), EDIT_TO_LOWERCASE); sub_menu->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KeyModifierMask::SHIFT | Key::F6), EDIT_CAPITALIZE); sub_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_menu->get_popup()->add_child(sub_menu); - edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case"); + edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "ConvertCase"); } edit_menu->get_popup()->add_child(highlighter_menu); - edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "highlighter_menu"); + edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "HighlighterMenu"); highlighter_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_change_syntax_highlighter)); edit_hb->add_child(search_menu); @@ -2309,13 +2309,13 @@ void ScriptTextEditor::_enable_code_editor() { goto_menu->get_popup()->add_separator(); goto_menu->get_popup()->add_child(bookmarks_menu); - goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks"); + goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "BookmarksMenu"); _update_bookmark_list(); bookmarks_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_bookmark_list)); bookmarks_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_bookmark_item_pressed)); goto_menu->get_popup()->add_child(breakpoints_menu); - goto_menu->get_popup()->add_submenu_item(TTR("Breakpoints"), "Breakpoints"); + goto_menu->get_popup()->add_submenu_item(TTR("Breakpoints"), "BreakpointsMenu"); _update_breakpoint_list(); breakpoints_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_breakpoint_list)); breakpoints_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_breakpoint_item_pressed)); @@ -2378,7 +2378,7 @@ ScriptTextEditor::ScriptTextEditor() { edit_menu->set_shortcut_context(this); highlighter_menu = memnew(PopupMenu); - highlighter_menu->set_name("highlighter_menu"); + highlighter_menu->set_name("HighlighterMenu"); Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter; plain_highlighter.instantiate(); @@ -2400,10 +2400,10 @@ ScriptTextEditor::ScriptTextEditor() { goto_menu->set_shortcut_context(this); bookmarks_menu = memnew(PopupMenu); - bookmarks_menu->set_name("Bookmarks"); + bookmarks_menu->set_name("BookmarksMenu"); breakpoints_menu = memnew(PopupMenu); - breakpoints_menu->set_name("Breakpoints"); + breakpoints_menu->set_name("BreakpointsMenu"); connection_info_dialog = memnew(ConnectionInfoDialog); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 2f186d00b4..18f5c31caa 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -65,9 +65,7 @@ void ShaderEditorPlugin::_update_shader_list() { } // When shader is deleted in filesystem dock, need this to correctly close shader editor. - if (!path.is_empty()) { - shader->set_meta("_edit_res_path", path); - } + edited_shader.path = path; bool unsaved = false; if (edited_shader.shader_editor) { @@ -578,9 +576,9 @@ void ShaderEditorPlugin::_window_changed(bool p_visible) { void ShaderEditorPlugin::_file_removed(const String &p_removed_file) { for (uint32_t i = 0; i < edited_shaders.size(); i++) { - const Ref<Shader> &shader = edited_shaders[i].shader; - if (shader->get_meta("_edit_res_path") == p_removed_file) { + if (edited_shaders[i].path == p_removed_file) { _close_shader(i); + break; } } } @@ -609,6 +607,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() { vb->add_child(menu_hb); file_menu = memnew(MenuButton); file_menu->set_text(TTR("File")); + file_menu->set_shortcut_context(main_split); file_menu->get_popup()->add_item(TTR("New Shader"), FILE_NEW); file_menu->get_popup()->add_item(TTR("New Shader Include"), FILE_NEW_INCLUDE); file_menu->get_popup()->add_separator(); diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 8b48140262..039afd61fe 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -50,6 +50,7 @@ class ShaderEditorPlugin : public EditorPlugin { Ref<ShaderInclude> shader_inc; TextShaderEditor *shader_editor = nullptr; VisualShaderEditor *visual_shader_editor = nullptr; + String path; }; LocalVector<EditedShader> edited_shaders; diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 01e9eb7a49..a1241ae662 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -1322,8 +1322,9 @@ void SpriteFramesEditor::_edit() { void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) { _update_stop_icon(); - if (!p_frames.is_valid()) { + if (p_frames.is_null()) { frames.unref(); + _remove_sprite_node(); hide(); return; } diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index b0a69cfb5c..c7945e44f0 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -662,18 +662,18 @@ TextEditor::TextEditor() { edit_menu->get_popup()->add_separator(); PopupMenu *convert_case = memnew(PopupMenu); - convert_case->set_name("convert_case"); + convert_case->set_name("ConvertCase"); edit_menu->get_popup()->add_child(convert_case); - edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case"); + edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "ConvertCase"); convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase")), EDIT_TO_UPPERCASE); convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase")), EDIT_TO_LOWERCASE); convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize")), EDIT_CAPITALIZE); convert_case->connect("id_pressed", callable_mp(this, &TextEditor::_edit_option)); highlighter_menu = memnew(PopupMenu); - highlighter_menu->set_name("highlighter_menu"); + highlighter_menu->set_name("HighlighterMenu"); edit_menu->get_popup()->add_child(highlighter_menu); - edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "highlighter_menu"); + edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "HighlighterMenu"); highlighter_menu->connect("id_pressed", callable_mp(this, &TextEditor::_change_syntax_highlighter)); Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter; @@ -696,9 +696,9 @@ TextEditor::TextEditor() { goto_menu->get_popup()->add_separator(); bookmarks_menu = memnew(PopupMenu); - bookmarks_menu->set_name("Bookmarks"); + bookmarks_menu->set_name("BookmarksMenu"); goto_menu->get_popup()->add_child(bookmarks_menu); - goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks"); + goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "BookmarksMenu"); _update_bookmark_list(); bookmarks_menu->connect("about_to_popup", callable_mp(this, &TextEditor::_update_bookmark_list)); bookmarks_menu->connect("index_pressed", callable_mp(this, &TextEditor::_bookmark_item_pressed)); diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp index 27f42608c6..3a2ddeb94e 100644 --- a/editor/plugins/text_shader_editor.cpp +++ b/editor/plugins/text_shader_editor.cpp @@ -1152,9 +1152,9 @@ TextShaderEditor::TextShaderEditor() { goto_menu->get_popup()->add_separator(); bookmarks_menu = memnew(PopupMenu); - bookmarks_menu->set_name("Bookmarks"); + bookmarks_menu->set_name("BookmarksMenu"); goto_menu->get_popup()->add_child(bookmarks_menu); - goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks"); + goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "BookmarksMenu"); _update_bookmark_list(); bookmarks_menu->connect("about_to_popup", callable_mp(this, &TextShaderEditor::_update_bookmark_list)); bookmarks_menu->connect("index_pressed", callable_mp(this, &TextShaderEditor::_bookmark_item_pressed)); diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp index 5dcdd9059e..9825be9ae8 100644 --- a/editor/plugins/theme_editor_preview.cpp +++ b/editor/plugins/theme_editor_preview.cpp @@ -354,8 +354,8 @@ DefaultThemeEditorPreview::DefaultThemeEditorPreview() { PopupMenu *test_submenu = memnew(PopupMenu); test_menu_button->get_popup()->add_child(test_submenu); - test_submenu->set_name("submenu"); - test_menu_button->get_popup()->add_submenu_item(TTR("Submenu"), "submenu"); + test_submenu->set_name("SubMenu"); + test_menu_button->get_popup()->add_submenu_item(TTR("Submenu"), "SubMenu"); test_submenu->add_item(TTR("Subitem 1")); test_submenu->add_item(TTR("Subitem 2")); first_vb->add_child(test_menu_button); diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index 6bac62d861..2fa54ac1dc 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -1443,18 +1443,18 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { extra_options_remove_branch_list = memnew(PopupMenu); extra_options_remove_branch_list->connect(SNAME("id_pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_branch_remove_confirm)); - extra_options_remove_branch_list->set_name("Remove Branch"); + extra_options_remove_branch_list->set_name("RemoveBranch"); extra_options->get_popup()->add_child(extra_options_remove_branch_list); - extra_options->get_popup()->add_submenu_item(TTR("Remove Branch"), "Remove Branch"); + extra_options->get_popup()->add_submenu_item(TTR("Remove Branch"), "RemoveBranch"); extra_options->get_popup()->add_separator(); extra_options->get_popup()->add_item(TTR("Create New Remote"), EXTRA_OPTION_CREATE_REMOTE); extra_options_remove_remote_list = memnew(PopupMenu); extra_options_remove_remote_list->connect(SNAME("id_pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_remote_remove_confirm)); - extra_options_remove_remote_list->set_name("Remove Remote"); + extra_options_remove_remote_list->set_name("RemoveRemote"); extra_options->get_popup()->add_child(extra_options_remove_remote_list); - extra_options->get_popup()->add_submenu_item(TTR("Remove Remote"), "Remove Remote"); + extra_options->get_popup()->add_submenu_item(TTR("Remove Remote"), "RemoveRemote"); change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_NEW] = TTR("New"); change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_MODIFIED] = TTR("Modified"); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index bd52deedac..d882c4652d 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -3056,7 +3056,7 @@ void SceneTreeDock::_add_children_to_popup(Object *p_obj, int p_depth) { Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(obj); if (menu->get_item_count() == 0) { - menu->add_submenu_item(TTR("Sub-Resources"), "Sub-Resources"); + menu->add_submenu_item(TTR("Sub-Resources"), "SubResources"); } menu_subresources->add_icon_item(icon, E.name.capitalize(), EDIT_SUBRESOURCE_BASE + subresources.size()); menu_subresources->set_item_indent(-1, p_depth); @@ -4142,7 +4142,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec menu->connect("id_pressed", callable_mp(this, &SceneTreeDock::_tool_selected).bind(false)); menu_subresources = memnew(PopupMenu); - menu_subresources->set_name("Sub-Resources"); + menu_subresources->set_name("SubResources"); menu_subresources->connect("id_pressed", callable_mp(this, &SceneTreeDock::_tool_selected).bind(false)); menu->add_child(menu_subresources); diff --git a/main/main.cpp b/main/main.cpp index 900f9b2714..7ca22d90ca 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1774,8 +1774,30 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph GLOBAL_DEF_RST("rendering/gl_compatibility/nvidia_disable_threaded_optimization", true); GLOBAL_DEF_RST("rendering/gl_compatibility/fallback_to_angle", true); GLOBAL_DEF_RST("rendering/gl_compatibility/fallback_to_native", true); + GLOBAL_DEF_RST("rendering/gl_compatibility/fallback_to_gles", true); - GLOBAL_DEF_RST(PropertyInfo(Variant::ARRAY, "rendering/gl_compatibility/force_angle_on_devices", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::DICTIONARY, PROPERTY_HINT_NONE, String())), Array()); + Array device_blocklist; + +#define BLOCK_DEVICE(m_vendor, m_name) \ + { \ + Dictionary device; \ + device["vendor"] = m_vendor; \ + device["name"] = m_name; \ + device_blocklist.push_back(device); \ + } + + // AMD GPUs. + BLOCK_DEVICE("ATI", "AMD Radeon(TM) R2 Graphics"); + BLOCK_DEVICE("ATI", "AMD Radeon(TM) R3 Graphics"); + BLOCK_DEVICE("ATI", "AMD Radeon HD 8400 / R3 Series"); + BLOCK_DEVICE("ATI", "AMD Radeon R5 M200 Series"); + BLOCK_DEVICE("ATI", "AMD Radeon R5 M230 Series"); + BLOCK_DEVICE("ATI", "AMD Radeon R5 M255"); + BLOCK_DEVICE("AMD", "AMD Radeon (TM) R5 M330"); + +#undef BLOCK_DEVICE + + GLOBAL_DEF_RST_NOVAL(PropertyInfo(Variant::ARRAY, "rendering/gl_compatibility/force_angle_on_devices", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::DICTIONARY, PROPERTY_HINT_NONE, String())), device_blocklist); } // Start with RenderingDevice-based backends. Should be included if any RD driver present. diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 3cc32bff10..56e4fa53d0 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1989,24 +1989,31 @@ const Variant CSharpInstance::get_rpc_config() const { void CSharpInstance::notification(int p_notification, bool p_reversed) { if (p_notification == Object::NOTIFICATION_PREDELETE) { - // When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose(). - // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed + if (base_ref_counted) { + // At this point, Dispose() was already called (manually or from the finalizer). + // The RefCounted wouldn't have reached 0 otherwise, since the managed side + // references it and Dispose() needs to be called to release it. + // However, this means C# RefCounted scripts can't receive NOTIFICATION_PREDELETE, but + // this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784 + return; + } + } else if (p_notification == Object::NOTIFICATION_PREDELETE_CLEANUP) { + // When NOTIFICATION_PREDELETE_CLEANUP is sent, we also take the chance to call Dispose(). + // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE_CLEANUP is guaranteed // to be sent at least once, which happens right before the call to the destructor. predelete_notified = true; if (base_ref_counted) { - // It's not safe to proceed if the owner derives RefCounted and the refcount reached 0. - // At this point, Dispose() was already called (manually or from the finalizer) so - // that's not a problem. The refcount wouldn't have reached 0 otherwise, since the - // managed side references it and Dispose() needs to be called to release it. - // However, this means C# RefCounted scripts can't receive NOTIFICATION_PREDELETE, but - // this is likely the case with GDScript as well: https://github.com/godotengine/godot/issues/6784 + // At this point, Dispose() was already called (manually or from the finalizer). + // The RefCounted wouldn't have reached 0 otherwise, since the managed side + // references it and Dispose() needs to be called to release it. return; } - _call_notification(p_notification, p_reversed); - + // NOTIFICATION_PREDELETE_CLEANUP is not sent to scripts. + // After calling Dispose() the C# instance can no longer be used, + // so it should be the last thing we do. GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose( gchandle.get_intptr(), /* okIfNull */ false); diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 6bdf207873..650afb4cdf 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -497,7 +497,10 @@ namespace GodotTools AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" }); - _menuPopup = new PopupMenu(); + _menuPopup = new PopupMenu + { + Name = "CSharpTools", + }; _menuPopup.Hide(); AddToolSubmenuItem("C#", _menuPopup); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs index 4ee452455e..215bb4df8c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2I.cs @@ -182,8 +182,8 @@ namespace Godot } // Constants - private static readonly Vector2I _min = new Vector2I(int.MinValue, int.MinValue); - private static readonly Vector2I _max = new Vector2I(int.MaxValue, int.MaxValue); + private static readonly Vector2I _minValue = new Vector2I(int.MinValue, int.MinValue); + private static readonly Vector2I _maxValue = new Vector2I(int.MaxValue, int.MaxValue); private static readonly Vector2I _zero = new Vector2I(0, 0); private static readonly Vector2I _one = new Vector2I(1, 1); @@ -197,12 +197,12 @@ namespace Godot /// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector2.Inf"/>. /// </summary> /// <value>Equivalent to <c>new Vector2I(int.MinValue, int.MinValue)</c>.</value> - public static Vector2I Min { get { return _min; } } + public static Vector2I MinValue { get { return _minValue; } } /// <summary> /// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector2.Inf"/>. /// </summary> /// <value>Equivalent to <c>new Vector2I(int.MaxValue, int.MaxValue)</c>.</value> - public static Vector2I Max { get { return _max; } } + public static Vector2I MaxValue { get { return _maxValue; } } /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs index db8ceb30e9..fe74ec8884 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3I.cs @@ -193,8 +193,8 @@ namespace Godot } // Constants - private static readonly Vector3I _min = new Vector3I(int.MinValue, int.MinValue, int.MinValue); - private static readonly Vector3I _max = new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue); + private static readonly Vector3I _minValue = new Vector3I(int.MinValue, int.MinValue, int.MinValue); + private static readonly Vector3I _maxValue = new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue); private static readonly Vector3I _zero = new Vector3I(0, 0, 0); private static readonly Vector3I _one = new Vector3I(1, 1, 1); @@ -210,12 +210,12 @@ namespace Godot /// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector3.Inf"/>. /// </summary> /// <value>Equivalent to <c>new Vector3I(int.MinValue, int.MinValue, int.MinValue)</c>.</value> - public static Vector3I Min { get { return _min; } } + public static Vector3I MinValue { get { return _minValue; } } /// <summary> /// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector3.Inf"/>. /// </summary> /// <value>Equivalent to <c>new Vector3I(int.MaxValue, int.MaxValue, int.MaxValue)</c>.</value> - public static Vector3I Max { get { return _max; } } + public static Vector3I MaxValue { get { return _maxValue; } } /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs index e75e996b04..a0a4393523 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4I.cs @@ -228,8 +228,8 @@ namespace Godot } // Constants - private static readonly Vector4I _min = new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue); - private static readonly Vector4I _max = new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue); + private static readonly Vector4I _minValue = new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue); + private static readonly Vector4I _maxValue = new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue); private static readonly Vector4I _zero = new Vector4I(0, 0, 0, 0); private static readonly Vector4I _one = new Vector4I(1, 1, 1, 1); @@ -238,12 +238,12 @@ namespace Godot /// Min vector, a vector with all components equal to <see cref="int.MinValue"/>. Can be used as a negative integer equivalent of <see cref="Vector4.Inf"/>. /// </summary> /// <value>Equivalent to <c>new Vector4I(int.MinValue, int.MinValue, int.MinValue, int.MinValue)</c>.</value> - public static Vector4I Min { get { return _min; } } + public static Vector4I MinValue { get { return _minValue; } } /// <summary> /// Max vector, a vector with all components equal to <see cref="int.MaxValue"/>. Can be used as an integer equivalent of <see cref="Vector4.Inf"/>. /// </summary> /// <value>Equivalent to <c>new Vector4I(int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue)</c>.</value> - public static Vector4I Max { get { return _max; } } + public static Vector4I MaxValue { get { return _maxValue; } } /// <summary> /// Zero vector, a vector with all components set to <c>0</c>. diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index d0e958164d..dc3ccccd08 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -804,6 +804,7 @@ bool OpenXRAPI::create_swapchains() { */ Size2 recommended_size = get_recommended_target_size(); + uint32_t sample_count = 1; // We start with our color swapchain... { @@ -827,7 +828,7 @@ bool OpenXRAPI::create_swapchains() { print_verbose(String("Using color swap chain format:") + get_swapchain_format_name(swapchain_format_to_use)); } - if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) { + if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain, &swapchains[OPENXR_SWAPCHAIN_COLOR].swapchain_graphics_data)) { return false; } } @@ -863,7 +864,7 @@ bool OpenXRAPI::create_swapchains() { // Note, if VK_FORMAT_D32_SFLOAT is used here but we're using the forward+ renderer, we should probably output a warning. - if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, view_configuration_views[0].recommendedSwapchainSampleCount, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) { + if (!create_swapchain(XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, swapchain_format_to_use, recommended_size.width, recommended_size.height, sample_count, view_count, swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain, &swapchains[OPENXR_SWAPCHAIN_DEPTH].swapchain_graphics_data)) { return false; } diff --git a/platform/linuxbsd/x11/detect_prime_x11.cpp b/platform/linuxbsd/x11/detect_prime_x11.cpp index 2b5776ce54..c2cb02b937 100644 --- a/platform/linuxbsd/x11/detect_prime_x11.cpp +++ b/platform/linuxbsd/x11/detect_prime_x11.cpp @@ -137,6 +137,19 @@ void create_context() { XFree(vi); } +int silent_error_handler(Display *display, XErrorEvent *error) { + static char message[1024]; + XGetErrorText(display, error->error_code, message, sizeof(message)); + print_verbose(vformat("XServer error: %s" + "\n Major opcode of failed request: %d" + "\n Serial number of failed request: %d" + "\n Current serial number in output stream: %d", + String::utf8(message), (uint64_t)error->request_code, (uint64_t)error->minor_code, (uint64_t)error->serial)); + + quick_exit(1); + return 0; +} + int detect_prime() { pid_t p; int priorities[2] = {}; @@ -189,6 +202,7 @@ int detect_prime() { // cleaning up these processes, and fork() makes a copy // of all globals. CoreGlobals::leak_reporting_enabled = false; + XSetErrorHandler(&silent_error_handler); char string[201]; diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 55c637fc93..3bafdfb53d 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -6065,33 +6065,36 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode } } if (rendering_driver == "opengl3") { - GLManager_X11::ContextType opengl_api_type = GLManager_X11::GLES_3_0_COMPATIBLE; - - gl_manager = memnew(GLManager_X11(p_resolution, opengl_api_type)); - - if (gl_manager->initialize(x11_display) != OK) { + gl_manager = memnew(GLManager_X11(p_resolution, GLManager_X11::GLES_3_0_COMPATIBLE)); + if (gl_manager->initialize(x11_display) != OK || gl_manager->open_display(x11_display) != OK) { memdelete(gl_manager); gl_manager = nullptr; - r_error = ERR_UNAVAILABLE; - return; + bool fallback = GLOBAL_GET("rendering/gl_compatibility/fallback_to_gles"); + if (fallback) { + WARN_PRINT("Your video card drivers seem not to support the required OpenGL version, switching to OpenGLES."); + rendering_driver = "opengl3_es"; + } else { + r_error = ERR_UNAVAILABLE; + ERR_FAIL_MSG("Could not initialize OpenGL."); + } + } else { + driver_found = true; + RasterizerGLES3::make_current(true); } - driver_found = true; - - RasterizerGLES3::make_current(true); } + if (rendering_driver == "opengl3_es") { gl_manager_egl = memnew(GLManagerEGL_X11); - if (gl_manager_egl->initialize() != OK) { memdelete(gl_manager_egl); gl_manager_egl = nullptr; r_error = ERR_UNAVAILABLE; - return; + ERR_FAIL_MSG("Could not initialize OpenGLES."); } driver_found = true; - RasterizerGLES3::make_current(false); } + #endif if (!driver_found) { r_error = ERR_UNAVAILABLE; diff --git a/platform/linuxbsd/x11/gl_manager_x11.cpp b/platform/linuxbsd/x11/gl_manager_x11.cpp index 95947301cf..602dd784e0 100644 --- a/platform/linuxbsd/x11/gl_manager_x11.cpp +++ b/platform/linuxbsd/x11/gl_manager_x11.cpp @@ -208,6 +208,15 @@ XVisualInfo GLManager_X11::get_vi(Display *p_display, Error &r_error) { return _displays[display_id].x_vi; } +Error GLManager_X11::open_display(Display *p_display) { + int gldisplay_id = _find_or_create_display(p_display); + if (gldisplay_id < 0) { + return ERR_CANT_CREATE; + } else { + return OK; + } +} + Error GLManager_X11::window_create(DisplayServer::WindowID p_window_id, ::Window p_window, Display *p_display, int p_width, int p_height) { // make sure vector is big enough... // we can mirror the external vector, it is simpler diff --git a/platform/linuxbsd/x11/gl_manager_x11.h b/platform/linuxbsd/x11/gl_manager_x11.h index d3a25506a8..235c7d22f9 100644 --- a/platform/linuxbsd/x11/gl_manager_x11.h +++ b/platform/linuxbsd/x11/gl_manager_x11.h @@ -129,6 +129,7 @@ public: void *get_glx_context(DisplayServer::WindowID p_window_id); + Error open_display(Display *p_display); GLManager_X11(const Vector2i &p_size, ContextType p_context_type); ~GLManager_X11(); }; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 6015aa96cd..4b545966d2 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -4509,8 +4509,8 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM WARN_PRINT("Your video card drivers seem not to support the required Metal version, switching to native OpenGL."); rendering_driver = "opengl3"; } else { - ERR_FAIL_MSG("Could not initialize OpenGL."); - return; + r_error = ERR_UNAVAILABLE; + ERR_FAIL_MSG("Could not initialize ANGLE OpenGL."); } } } @@ -4521,8 +4521,7 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM memdelete(gl_manager_legacy); gl_manager_legacy = nullptr; r_error = ERR_UNAVAILABLE; - ERR_FAIL_MSG("Could not initialize OpenGL."); - return; + ERR_FAIL_MSG("Could not initialize native OpenGL."); } } #endif diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index bb0b64ba10..55a6c290f1 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -1692,7 +1692,9 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window) SystemParametersInfoA(SPI_SETMOUSETRAILS, restore_mouse_trails, 0, 0); restore_mouse_trails = 0; } - } else if (p_mode == WINDOW_MODE_WINDOWED) { + } + + if (p_mode == WINDOW_MODE_WINDOWED) { ShowWindow(wd.hWnd, SW_RESTORE); wd.maximized = false; wd.minimized = false; @@ -4656,7 +4658,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win Array device_list = GLOBAL_GET("rendering/gl_compatibility/force_angle_on_devices"); for (int i = 0; i < device_list.size(); i++) { const Dictionary &device = device_list[i]; - if (device.has("vendor") && device.has("name") && device["vendor"].operator String().to_upper() == gl_info["vendor"].operator String().to_upper() && (device["name"] == "*" || device["name"].operator String().to_upper() == gl_info["name"].operator String().to_upper())) { + if (device.has("vendor") && device.has("name") && gl_info["vendor"].operator String().to_upper().contains(device["vendor"].operator String().to_upper()) && (device["name"] == "*" || gl_info["name"].operator String().to_upper().contains(device["name"].operator String().to_upper()))) { force_angle = true; break; } diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 1f70d4b558..1ea342c3f4 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -2498,6 +2498,11 @@ Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const { } TileMapLayer::~TileMapLayer() { + if (!tile_map_node) { + // Temporary layer. + return; + } + in_destructor = true; clear(); internal_update(); @@ -3714,16 +3719,88 @@ bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { void TileMap::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + +#define MAKE_LAYER_PROPERTY(m_type, m_name, m_hint) \ + { \ + const String property_name = vformat("layer_%d/" m_name, i); \ + p_list->push_back(PropertyInfo(m_type, property_name, PROPERTY_HINT_NONE, m_hint, (get(property_name) == property_get_revert(property_name)) ? PROPERTY_USAGE_EDITOR : PROPERTY_USAGE_DEFAULT)); \ + } + for (unsigned int i = 0; i < layers.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("layer_%d/name", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/enabled", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::COLOR, vformat("layer_%d/modulate", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/y_sort_enabled", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/y_sort_origin", i), PROPERTY_HINT_NONE, "suffix:px")); - p_list->push_back(PropertyInfo(Variant::INT, vformat("layer_%d/z_index", i), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::BOOL, vformat("layer_%d/navigation_enabled", i), PROPERTY_HINT_NONE)); + MAKE_LAYER_PROPERTY(Variant::STRING, "name", ""); + MAKE_LAYER_PROPERTY(Variant::BOOL, "enabled", ""); + MAKE_LAYER_PROPERTY(Variant::COLOR, "modulate", ""); + MAKE_LAYER_PROPERTY(Variant::BOOL, "y_sort_enabled", ""); + MAKE_LAYER_PROPERTY(Variant::INT, "y_sort_origin", "suffix:px"); + MAKE_LAYER_PROPERTY(Variant::INT, "z_index", ""); + MAKE_LAYER_PROPERTY(Variant::BOOL, "navigation_enabled", ""); p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); } + +#undef MAKE_LAYER_PROPERTY +} + +bool TileMap::_property_can_revert(const StringName &p_name) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() == 2 && components[0].begins_with("layer_")) { + int index = components[0].trim_prefix("layer_").to_int(); + if (index <= 0 || index >= (int)layers.size()) { + return false; + } + + if (components[1] == "name") { + return layers[index]->get_name() != default_layer->get_name(); + } else if (components[1] == "enabled") { + return layers[index]->is_enabled() != default_layer->is_enabled(); + } else if (components[1] == "modulate") { + return layers[index]->get_modulate() != default_layer->get_modulate(); + } else if (components[1] == "y_sort_enabled") { + return layers[index]->is_y_sort_enabled() != default_layer->is_y_sort_enabled(); + } else if (components[1] == "y_sort_origin") { + return layers[index]->get_y_sort_origin() != default_layer->get_y_sort_origin(); + } else if (components[1] == "z_index") { + return layers[index]->get_z_index() != default_layer->get_z_index(); + } else if (components[1] == "navigation_enabled") { + return layers[index]->is_navigation_enabled() != default_layer->is_navigation_enabled(); + } + } + + return false; +} + +bool TileMap::_property_get_revert(const StringName &p_name, Variant &r_property) const { + Vector<String> components = String(p_name).split("/", true, 2); + if (components.size() == 2 && components[0].begins_with("layer_")) { + int index = components[0].trim_prefix("layer_").to_int(); + if (index <= 0 || index >= (int)layers.size()) { + return false; + } + + if (components[1] == "name") { + r_property = default_layer->get_name(); + return true; + } else if (components[1] == "enabled") { + r_property = default_layer->is_enabled(); + return true; + } else if (components[1] == "modulate") { + r_property = default_layer->get_modulate(); + return true; + } else if (components[1] == "y_sort_enabled") { + r_property = default_layer->is_y_sort_enabled(); + return true; + } else if (components[1] == "y_sort_origin") { + r_property = default_layer->get_y_sort_origin(); + return true; + } else if (components[1] == "z_index") { + r_property = default_layer->get_z_index(); + return true; + } else if (components[1] == "navigation_enabled") { + r_property = default_layer->is_navigation_enabled(); + return true; + } + } + + return false; } Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { @@ -4747,6 +4824,8 @@ TileMap::TileMap() { new_layer->set_tile_map(this); new_layer->set_layer_index_in_tile_map_node(0); layers.push_back(new_layer); + + default_layer.instantiate(); } TileMap::~TileMap() { diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index a16595629c..1136e4190d 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -461,6 +461,7 @@ private: // Layers. LocalVector<Ref<TileMapLayer>> layers; + Ref<TileMapLayer> default_layer; // Dummy layer to fetch default values. int selected_layer = -1; bool pending_update = false; @@ -479,6 +480,8 @@ protected: 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; + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; void _notification(int p_what); static void _bind_methods(); diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index c778129eb6..32028bcf28 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -104,7 +104,15 @@ Ref<PropertyTweener> Tween::tween_property(const Object *p_target, const NodePat CHECK_VALID(); Vector<StringName> property_subnames = p_property.get_as_property_path().get_subnames(); - if (!_validate_type_match(p_target->get_indexed(property_subnames), p_to)) { +#ifdef DEBUG_ENABLED + bool prop_valid; + const Variant &prop_value = p_target->get_indexed(property_subnames, &prop_valid); + ERR_FAIL_COND_V_MSG(!prop_valid, nullptr, vformat("The tweened property \"%s\" does not exist in object \"%s\".", p_property, p_target)); +#else + const Variant &prop_value = p_target->get_indexed(property_subnames); +#endif + + if (!_validate_type_match(prop_value, p_to)) { return nullptr; } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index c7ff5980cb..ed54bd000c 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1653,6 +1653,7 @@ void Control::set_custom_minimum_size(const Size2 &p_custom) { data.custom_minimum_size = p_custom; update_minimum_size(); + update_configuration_warnings(); } Size2 Control::get_custom_minimum_size() const { @@ -1831,9 +1832,18 @@ bool Control::has_point(const Point2 &p_point) const { void Control::set_mouse_filter(MouseFilter p_filter) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_INDEX(p_filter, 3); + + if (data.mouse_filter == p_filter) { + return; + } + data.mouse_filter = p_filter; notify_property_list_changed(); update_configuration_warnings(); + + if (get_viewport()) { + get_viewport()->_gui_update_mouse_over(); + } } Control::MouseFilter Control::get_mouse_filter() const { @@ -3568,6 +3578,8 @@ void Control::_bind_methods() { BIND_CONSTANT(NOTIFICATION_RESIZED); BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER); BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT); + BIND_CONSTANT(NOTIFICATION_MOUSE_ENTER_SELF); + BIND_CONSTANT(NOTIFICATION_MOUSE_EXIT_SELF); BIND_CONSTANT(NOTIFICATION_FOCUS_ENTER); BIND_CONSTANT(NOTIFICATION_FOCUS_EXIT); BIND_CONSTANT(NOTIFICATION_THEME_CHANGED); diff --git a/scene/gui/control.h b/scene/gui/control.h index abbdc42fa4..db1bd3a346 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -368,6 +368,8 @@ public: NOTIFICATION_SCROLL_BEGIN = 47, NOTIFICATION_SCROLL_END = 48, NOTIFICATION_LAYOUT_DIRECTION_CHANGED = 49, + NOTIFICATION_MOUSE_ENTER_SELF = 60, + NOTIFICATION_MOUSE_EXIT_SELF = 61, }; // Editor plugin interoperability. diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 0d48cb1549..2fbd29b048 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/string/print_string.h" #include "core/string/translation.h" +#include "scene/gui/container.h" #include "scene/theme/theme_db.h" #include "servers/text_server.h" @@ -44,6 +45,7 @@ void Label::set_autowrap_mode(TextServer::AutowrapMode p_mode) { autowrap_mode = p_mode; lines_dirty = true; queue_redraw(); + update_configuration_warnings(); if (clip || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING) { update_minimum_size(); @@ -327,6 +329,19 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col PackedStringArray Label::get_configuration_warnings() const { PackedStringArray warnings = Control::get_configuration_warnings(); + // FIXME: This is not ideal and the sizing model should be fixed, + // but for now we have to warn about this impossible to resolve combination. + // See GH-83546. + if (is_inside_tree() && get_tree()->get_edited_scene_root() != this) { + // If the Label happens to be the root node of the edited scene, we don't need + // to check what its parent is. It's going to be some node from the editor tree + // and it can be a container, but that makes no difference to the user. + Container *parent_container = Object::cast_to<Container>(get_parent_control()); + if (parent_container && autowrap_mode != TextServer::AUTOWRAP_OFF && get_custom_minimum_size() == Size2()) { + warnings.push_back(RTR("Labels with autowrapping enabled must have a custom minimum size configured to work correctly inside a container.")); + } + } + // Ensure that the font can render all of the required glyphs. Ref<Font> font; if (settings.is_valid()) { diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 0cda27ec24..d6b8dd0202 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -1487,7 +1487,11 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons } void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) { - ERR_FAIL_COND_MSG(p_submenu.validate_node_name() != p_submenu, "Invalid node name for submenu, the following characters are not allowed:\n" + String::get_invalid_node_name_characters()); + String submenu_name_safe = p_submenu.replace("@", "_"); // Allow special characters for auto-generated names. + if (submenu_name_safe.validate_node_name() != submenu_name_safe) { + ERR_FAIL_MSG(vformat("Invalid node name '%s' for a submenu, the following characters are not allowed:\n%s", p_submenu, String::get_invalid_node_name_characters(true))); + } + Item item; item.text = p_label; item.xl_text = atr(p_label); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 31ed5984a4..30a468dfc5 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -830,37 +830,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o prefix = segment + prefix; } } - if (!prefix.is_empty()) { - Ref<Font> font = theme_cache.normal_font; - int font_size = theme_cache.normal_font_size; - - ItemFont *font_it = _find_font(l.from); - if (font_it) { - if (font_it->font.is_valid()) { - font = font_it->font; - } - if (font_it->font_size > 0) { - font_size = font_it->font_size; - } - } - ItemFontSize *font_size_it = _find_font_size(l.from); - if (font_size_it && font_size_it->font_size > 0) { - font_size = font_size_it->font_size; - } - if (rtl) { - float offx = 0.0f; - if (!lrtl && p_frame == main) { // Skip Scrollbar. - offx -= scroll_w; - } - font->draw_string(ci, p_ofs + Vector2(p_width - l.offset.x + offx, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color)); - } else { - float offx = 0.0f; - if (lrtl && p_frame == main) { // Skip Scrollbar. - offx += scroll_w; - } - font->draw_string(ci, p_ofs + Vector2(offx, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color)); - } - } // Draw dropcap. int dc_lines = l.text_buf->get_dropcap_lines(); @@ -924,6 +893,30 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } break; } + if (!prefix.is_empty() && line == 0) { + Ref<Font> font = theme_cache.normal_font; + int font_size = theme_cache.normal_font_size; + + ItemFont *font_it = _find_font(l.from); + if (font_it) { + if (font_it->font.is_valid()) { + font = font_it->font; + } + if (font_it->font_size > 0) { + font_size = font_it->font_size; + } + } + ItemFontSize *font_size_it = _find_font_size(l.from); + if (font_size_it && font_size_it->font_size > 0) { + font_size = font_size_it->font_size; + } + if (rtl) { + font->draw_string(ci, p_ofs + Vector2(off.x + length, l.text_buf->get_line_ascent(0)), " " + prefix, HORIZONTAL_ALIGNMENT_LEFT, l.offset.x, font_size, _find_color(l.from, p_base_color)); + } else { + font->draw_string(ci, p_ofs + Vector2(off.x - l.offset.x, l.text_buf->get_line_ascent(0)), prefix + " ", HORIZONTAL_ALIGNMENT_RIGHT, l.offset.x, font_size, _find_color(l.from, p_base_color)); + } + } + if (line <= dc_lines) { if (rtl) { off.x -= h_off; diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp index a350b97bc8..4ee81e5cb0 100644 --- a/scene/main/canvas_item.cpp +++ b/scene/main/canvas_item.cpp @@ -462,6 +462,10 @@ void CanvasItem::set_as_top_level(bool p_top_level) { _enter_canvas(); _notify_transform(); + + if (get_viewport()) { + get_viewport()->canvas_item_top_level_changed(); + } } void CanvasItem::_top_level_changed() { diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 2b28f21f57..43bdb1395b 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -2408,8 +2408,8 @@ void Viewport::_gui_hide_control(Control *p_control) { if (gui.key_focus == p_control) { gui_release_focus(); } - if (gui.mouse_over == p_control) { - _drop_mouse_over(); + if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) { + _drop_mouse_over(p_control->get_parent_control()); } if (gui.drag_mouse_over == p_control) { gui.drag_mouse_over = nullptr; @@ -2431,8 +2431,8 @@ void Viewport::_gui_remove_control(Control *p_control) { if (gui.key_focus == p_control) { gui.key_focus = nullptr; } - if (gui.mouse_over == p_control) { - _drop_mouse_over(); + if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) { + _drop_mouse_over(p_control->get_parent_control()); } if (gui.drag_mouse_over == p_control) { gui.drag_mouse_over = nullptr; @@ -2442,6 +2442,94 @@ void Viewport::_gui_remove_control(Control *p_control) { } } +void Viewport::canvas_item_top_level_changed() { + _gui_update_mouse_over(); +} + +void Viewport::_gui_update_mouse_over() { + if (gui.mouse_over == nullptr || gui.mouse_over_hierarchy.is_empty()) { + return; + } + + // Rebuild the mouse over hierarchy. + LocalVector<Control *> new_mouse_over_hierarchy; + LocalVector<Control *> needs_enter; + LocalVector<int> needs_exit; + + CanvasItem *ancestor = gui.mouse_over; + bool removing = false; + bool reached_top = false; + while (ancestor) { + Control *ancestor_control = Object::cast_to<Control>(ancestor); + if (ancestor_control) { + int found = gui.mouse_over_hierarchy.find(ancestor_control); + if (found >= 0) { + // Remove the node if the propagation chain has been broken or it is now MOUSE_FILTER_IGNORE. + if (removing || ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_IGNORE) { + needs_exit.push_back(found); + } + } + if (found == 0) { + if (removing) { + // Stop if the chain has been broken and the top of the hierarchy has been reached. + break; + } + reached_top = true; + } + if (!removing && ancestor_control->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) { + new_mouse_over_hierarchy.push_back(ancestor_control); + // Add the node if it was not found and it is now not MOUSE_FILTER_IGNORE. + if (found < 0) { + needs_enter.push_back(ancestor_control); + } + } + if (ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_STOP) { + // MOUSE_FILTER_STOP breaks the propagation chain. + if (reached_top) { + break; + } + removing = true; + } + } + if (ancestor->is_set_as_top_level()) { + // Top level breaks the propagation chain. + if (reached_top) { + break; + } else { + removing = true; + ancestor = Object::cast_to<CanvasItem>(ancestor->get_parent()); + continue; + } + } + ancestor = ancestor->get_parent_item(); + } + if (needs_exit.is_empty() && needs_enter.is_empty()) { + return; + } + + // Send Mouse Exit Self notification. + if (gui.mouse_over && !needs_exit.is_empty() && needs_exit[0] == (int)gui.mouse_over_hierarchy.size() - 1) { + gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF); + gui.mouse_over = nullptr; + } + + // Send Mouse Exit notifications. + for (int exit_control_index : needs_exit) { + gui.mouse_over_hierarchy[exit_control_index]->notification(Control::NOTIFICATION_MOUSE_EXIT); + } + + // Update the mouse over hierarchy. + gui.mouse_over_hierarchy.resize(new_mouse_over_hierarchy.size()); + for (int i = 0; i < (int)new_mouse_over_hierarchy.size(); i++) { + gui.mouse_over_hierarchy[i] = new_mouse_over_hierarchy[new_mouse_over_hierarchy.size() - 1 - i]; + } + + // Send Mouse Enter notifications. + for (int i = needs_enter.size() - 1; i >= 0; i--) { + needs_enter[i]->notification(Control::NOTIFICATION_MOUSE_ENTER); + } +} + Window *Viewport::get_base_window() const { ERR_READ_THREAD_GUARD_V(nullptr); ERR_FAIL_COND_V(!is_inside_tree(), nullptr); @@ -3069,16 +3157,58 @@ void Viewport::_update_mouse_over(Vector2 p_pos) { // Look for Controls at mouse position. Control *over = gui_find_control(p_pos); bool notify_embedded_viewports = false; - if (over != gui.mouse_over) { - if (gui.mouse_over) { - _drop_mouse_over(); + if (over != gui.mouse_over || (!over && !gui.mouse_over_hierarchy.is_empty())) { + // Find the common ancestor of `gui.mouse_over` and `over`. + Control *common_ancestor = nullptr; + LocalVector<Control *> over_ancestors; + + if (over) { + // Get all ancestors that the mouse is currently over and need an enter signal. + CanvasItem *ancestor = over; + while (ancestor) { + Control *ancestor_control = Object::cast_to<Control>(ancestor); + if (ancestor_control) { + if (ancestor_control->get_mouse_filter() != Control::MOUSE_FILTER_IGNORE) { + int found = gui.mouse_over_hierarchy.find(ancestor_control); + if (found >= 0) { + common_ancestor = gui.mouse_over_hierarchy[found]; + break; + } + over_ancestors.push_back(ancestor_control); + } + if (ancestor_control->get_mouse_filter() == Control::MOUSE_FILTER_STOP) { + // MOUSE_FILTER_STOP breaks the propagation chain. + break; + } + } + if (ancestor->is_set_as_top_level()) { + // Top level breaks the propagation chain. + break; + } + ancestor = ancestor->get_parent_item(); + } + } + + if (gui.mouse_over || !gui.mouse_over_hierarchy.is_empty()) { + // Send Mouse Exit Self and Mouse Exit notifications. + _drop_mouse_over(common_ancestor); } else { _drop_physics_mouseover(); } - gui.mouse_over = over; if (over) { - over->notification(Control::NOTIFICATION_MOUSE_ENTER); + gui.mouse_over = over; + gui.mouse_over_hierarchy.reserve(gui.mouse_over_hierarchy.size() + over_ancestors.size()); + + // Send Mouse Enter notifications to parents first. + for (int i = over_ancestors.size() - 1; i >= 0; i--) { + over_ancestors[i]->notification(Control::NOTIFICATION_MOUSE_ENTER); + gui.mouse_over_hierarchy.push_back(over_ancestors[i]); + } + + // Send Mouse Enter Self notification. + gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_ENTER_SELF); + notify_embedded_viewports = true; } } @@ -3119,7 +3249,7 @@ void Viewport::_mouse_leave_viewport() { notification(NOTIFICATION_VP_MOUSE_EXIT); } -void Viewport::_drop_mouse_over() { +void Viewport::_drop_mouse_over(Control *p_until_control) { _gui_cancel_tooltip(); SubViewportContainer *c = Object::cast_to<SubViewportContainer>(gui.mouse_over); if (c) { @@ -3131,10 +3261,19 @@ void Viewport::_drop_mouse_over() { v->_mouse_leave_viewport(); } } - if (gui.mouse_over->is_inside_tree()) { - gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT); + if (gui.mouse_over && gui.mouse_over->is_inside_tree()) { + gui.mouse_over->notification(Control::NOTIFICATION_MOUSE_EXIT_SELF); } gui.mouse_over = nullptr; + + // Send Mouse Exit notifications to children first. Don't send to p_until_control or above. + int notification_until = p_until_control ? gui.mouse_over_hierarchy.find(p_until_control) + 1 : 0; + for (int i = gui.mouse_over_hierarchy.size() - 1; i >= notification_until; i--) { + if (gui.mouse_over_hierarchy[i]->is_inside_tree()) { + gui.mouse_over_hierarchy[i]->notification(Control::NOTIFICATION_MOUSE_EXIT); + } + } + gui.mouse_over_hierarchy.resize(notification_until); } void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) { diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 65777c973f..82a9bfc438 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -361,6 +361,7 @@ private: BitField<MouseButtonMask> mouse_focus_mask; Control *key_focus = nullptr; Control *mouse_over = nullptr; + LocalVector<Control *> mouse_over_hierarchy; Window *subwindow_over = nullptr; // mouse_over and subwindow_over are mutually exclusive. At all times at least one of them is nullptr. Window *windowmanager_window_over = nullptr; // Only used in root Viewport. Control *drag_mouse_over = nullptr; @@ -429,6 +430,7 @@ private: void _gui_remove_control(Control *p_control); void _gui_hide_control(Control *p_control); + void _gui_update_mouse_over(); void _gui_force_drag(Control *p_base, const Variant &p_data, Control *p_control); void _gui_set_drag_preview(Control *p_base, Control *p_control); @@ -455,7 +457,7 @@ private: void _canvas_layer_add(CanvasLayer *p_canvas_layer); void _canvas_layer_remove(CanvasLayer *p_canvas_layer); - void _drop_mouse_over(); + void _drop_mouse_over(Control *p_until_control = nullptr); void _drop_mouse_focus(); void _drop_physics_mouseover(bool p_paused_only = false); @@ -494,6 +496,7 @@ protected: public: void canvas_parent_mark_dirty(Node *p_node); + void canvas_item_top_level_changed(); uint64_t get_processed_events_count() const { return event_count; } diff --git a/scene/resources/immediate_mesh.cpp b/scene/resources/immediate_mesh.cpp index 3507df8bd8..dde556c264 100644 --- a/scene/resources/immediate_mesh.cpp +++ b/scene/resources/immediate_mesh.cpp @@ -166,7 +166,7 @@ void ImmediateMesh::surface_end() { normal_tangent_stride += sizeof(uint32_t); } uint32_t tangent_offset = 0; - if (uses_tangents) { + if (uses_tangents || uses_normals) { format |= ARRAY_FORMAT_TANGENT; tangent_offset = vertex_stride * vertices.size() + normal_tangent_stride; normal_tangent_stride += sizeof(uint32_t); @@ -202,9 +202,16 @@ void ImmediateMesh::surface_end() { *normal = value; } - if (uses_tangents) { + if (uses_tangents || uses_normals) { uint32_t *tangent = (uint32_t *)&surface_vertex_ptr[i * normal_tangent_stride + tangent_offset]; - Vector2 t = tangents[i].normal.octahedron_tangent_encode(tangents[i].d); + Vector2 t; + if (uses_tangents) { + t = tangents[i].normal.octahedron_tangent_encode(tangents[i].d); + } else { + Vector3 tan = Vector3(0.0, 1.0, 0.0).cross(normals[i].normalized()); + t = tan.octahedron_tangent_encode(1.0); + } + uint32_t value = 0; value |= (uint16_t)CLAMP(t.x * 65535, 0, 65535); value |= (uint16_t)CLAMP(t.y * 65535, 0, 65535) << 16; diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index e5467ea72d..2397249ca5 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1739,7 +1739,12 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co _setup_voxelgis(*p_render_data->voxel_gi_instances); _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); - _update_render_base_uniform_set(rb->get_samplers()); // May have changed due to the above (light buffer enlarged, as an example). + // May have changed due to the above (light buffer enlarged, as an example). + if (is_reflection_probe) { + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); + } else { + _update_render_base_uniform_set(rb->get_samplers(), BASE_UNIFORM_SET_CACHE_VIEWPORT); + } _fill_render_list(RENDER_LIST_OPAQUE, p_render_data, PASS_MODE_COLOR, using_sdfgi, using_sdfgi || using_voxelgi, using_motion_pass); render_list[RENDER_LIST_OPAQUE].sort_by_key(); @@ -1970,7 +1975,11 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co p_render_data->scene_data->opaque_prepass_threshold = 0.0f; // Shadow pass can change the base uniform set samplers. - _update_render_base_uniform_set(rb->get_samplers()); + if (is_reflection_probe) { + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); + } else { + _update_render_base_uniform_set(rb->get_samplers(), BASE_UNIFORM_SET_CACHE_VIEWPORT); + } _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, true, using_motion_pass); @@ -2495,7 +2504,7 @@ void RenderForwardClustered::_render_shadow_pass(RID p_light, RID p_shadow_atlas void RenderForwardClustered::_render_shadow_begin() { scene_state.shadow_passes.clear(); RD::get_singleton()->draw_command_begin_label("Shadow Setup"); - _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); render_list[RENDER_LIST_SECONDARY].clear(); scene_state.instance_data[RENDER_LIST_SECONDARY].clear(); @@ -2619,7 +2628,7 @@ void RenderForwardClustered::_render_particle_collider_heightfield(RID p_fb, con render_data.cluster_max_elements = 32; render_data.instances = &p_instances; - _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); _setup_environment(&render_data, true, Vector2(1, 1), true, Color(), false, false, false); @@ -2665,7 +2674,7 @@ void RenderForwardClustered::_render_material(const Transform3D &p_cam_transform scene_shader.enable_advanced_shader_group(); - _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); @@ -2716,7 +2725,7 @@ void RenderForwardClustered::_render_uv2(const PagedArray<RenderGeometryInstance scene_shader.enable_advanced_shader_group(); - _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); _setup_environment(&render_data, true, Vector2(1, 1), false, Color()); @@ -2785,7 +2794,7 @@ void RenderForwardClustered::_render_sdfgi(Ref<RenderSceneBuffersRD> p_render_bu render_data.cluster_max_elements = 32; render_data.instances = &p_instances; - _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); PassMode pass_mode = PASS_MODE_SDF; _fill_render_list(RENDER_LIST_SECONDARY, &render_data, pass_mode); @@ -2859,21 +2868,23 @@ void RenderForwardClustered::_render_sdfgi(Ref<RenderSceneBuffersRD> p_render_bu } void RenderForwardClustered::base_uniforms_changed() { - if (!render_base_uniform_set.is_null() && RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set)) { - RD::get_singleton()->free(render_base_uniform_set); + for (int i = 0; i < BASE_UNIFORM_SET_CACHE_MAX; i++) { + if (!render_base_uniform_set_cache[i].is_null() && RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set_cache[i])) { + RD::get_singleton()->free(render_base_uniform_set_cache[i]); + } + render_base_uniform_set_cache[i] = RID(); } - render_base_uniform_set = RID(); } -void RenderForwardClustered::_update_render_base_uniform_set(const RendererRD::MaterialStorage::Samplers &p_samplers) { +void RenderForwardClustered::_update_render_base_uniform_set(const RendererRD::MaterialStorage::Samplers &p_samplers, BaseUniformSetCache p_cache_index) { RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton(); - if (render_base_uniform_set.is_null() || !RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set) || (lightmap_texture_array_version != light_storage->lightmap_array_get_version())) { - if (render_base_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set)) { - RD::get_singleton()->free(render_base_uniform_set); + if (render_base_uniform_set_cache[p_cache_index].is_null() || !RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set_cache[p_cache_index]) || (lightmap_texture_array_version_cache[p_cache_index] != light_storage->lightmap_array_get_version())) { + if (render_base_uniform_set_cache[p_cache_index].is_valid() && RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set_cache[p_cache_index])) { + RD::get_singleton()->free(render_base_uniform_set_cache[p_cache_index]); } - lightmap_texture_array_version = light_storage->lightmap_array_get_version(); + lightmap_texture_array_version_cache[p_cache_index] = light_storage->lightmap_array_get_version(); Vector<RD::Uniform> uniforms; @@ -3030,8 +3041,9 @@ void RenderForwardClustered::_update_render_base_uniform_set(const RendererRD::M uniforms.append_array(p_samplers.get_uniforms(SAMPLERS_BINDING_FIRST_INDEX)); - render_base_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, scene_shader.default_shader_rd, SCENE_UNIFORM_SET); + render_base_uniform_set_cache[p_cache_index] = RD::get_singleton()->uniform_set_create(uniforms, scene_shader.default_shader_rd, SCENE_UNIFORM_SET); } + render_base_uniform_set = render_base_uniform_set_cache[p_cache_index]; } RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_render_list, const RenderDataRD *p_render_data, RID p_radiance_texture, bool p_use_directional_shadow_atlas, int p_index) { diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h index 6077fb4bde..bedf119210 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h @@ -158,11 +158,20 @@ class RenderForwardClustered : public RendererSceneRenderRD { virtual void setup_render_buffer_data(Ref<RenderSceneBuffersRD> p_render_buffers) override; + enum BaseUniformSetCache { + BASE_UNIFORM_SET_CACHE_VIEWPORT, + BASE_UNIFORM_SET_CACHE_DEFAULT, + BASE_UNIFORM_SET_CACHE_MAX + }; + RID render_base_uniform_set; + // One for custom samplers, one for default samplers. + // Need to switch between them as default is needed for probes, shadows, materials, etc. + RID render_base_uniform_set_cache[BASE_UNIFORM_SET_CACHE_MAX]; - uint64_t lightmap_texture_array_version = 0xFFFFFFFF; + uint64_t lightmap_texture_array_version_cache[BASE_UNIFORM_SET_CACHE_MAX] = { 0xFFFFFFFF, 0xFFFFFFFF }; - void _update_render_base_uniform_set(const RendererRD::MaterialStorage::Samplers &p_samplers); + void _update_render_base_uniform_set(const RendererRD::MaterialStorage::Samplers &p_samplers, BaseUniformSetCache p_cache_index); RID _setup_sdfgi_render_pass_uniform_set(RID p_albedo_texture, RID p_emission_texture, RID p_emission_aniso_texture, RID p_geom_facing_texture); RID _setup_render_pass_uniform_set(RenderListType p_render_list, const RenderDataRD *p_render_data, RID p_radiance_texture, bool p_use_directional_shadow_atlas = false, int p_index = 0); diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 99b416d575..462fc4b524 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -763,7 +763,12 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color _setup_lightmaps(p_render_data, *p_render_data->lightmaps, p_render_data->scene_data->cam_transform); _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, false); - _update_render_base_uniform_set(rb->get_samplers()); //may have changed due to the above (light buffer enlarged, as an example) + // May have changed due to the above (light buffer enlarged, as an example). + if (is_reflection_probe) { + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); + } else { + _update_render_base_uniform_set(rb->get_samplers(), BASE_UNIFORM_SET_CACHE_VIEWPORT); + } RD::get_singleton()->draw_command_end_label(); // Render Setup @@ -902,7 +907,11 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color p_render_data->scene_data->directional_light_count = p_render_data->directional_light_count; // Shadow pass can change the base uniform set samplers. - _update_render_base_uniform_set(rb->get_samplers()); + if (is_reflection_probe) { + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); + } else { + _update_render_base_uniform_set(rb->get_samplers(), BASE_UNIFORM_SET_CACHE_VIEWPORT); + } _setup_environment(p_render_data, is_reflection_probe, screen_size, !is_reflection_probe, p_default_bg_color, p_render_data->render_buffers.is_valid()); @@ -1264,7 +1273,7 @@ void RenderForwardMobile::_render_shadow_pass(RID p_light, RID p_shadow_atlas, i void RenderForwardMobile::_render_shadow_begin() { scene_state.shadow_passes.clear(); RD::get_singleton()->draw_command_begin_label("Shadow Setup"); - _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); render_list[RENDER_LIST_SECONDARY].clear(); } @@ -1371,7 +1380,7 @@ void RenderForwardMobile::_render_material(const Transform3D &p_cam_transform, c RD::get_singleton()->draw_command_begin_label("Render 3D Material"); - _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); RenderSceneDataRD scene_data; scene_data.cam_projection = p_cam_projection; @@ -1422,7 +1431,7 @@ void RenderForwardMobile::_render_uv2(const PagedArray<RenderGeometryInstance *> RD::get_singleton()->draw_command_begin_label("Render UV2"); - _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); RenderSceneDataRD scene_data; scene_data.dual_paraboloid_side = 0; @@ -1496,7 +1505,7 @@ void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const RD::get_singleton()->draw_command_begin_label("Render Collider Heightfield"); - _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default()); + _update_render_base_uniform_set(RendererRD::MaterialStorage::get_singleton()->samplers_rd_get_default(), BASE_UNIFORM_SET_CACHE_DEFAULT); RenderSceneDataRD scene_data; scene_data.cam_projection = p_cam_projection; @@ -1534,23 +1543,23 @@ void RenderForwardMobile::_render_particle_collider_heightfield(RID p_fb, const } void RenderForwardMobile::base_uniforms_changed() { - if (!render_base_uniform_set.is_null() && RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set)) { - RD::get_singleton()->free(render_base_uniform_set); + for (int i = 0; i < BASE_UNIFORM_SET_CACHE_MAX; i++) { + if (!render_base_uniform_set_cache[i].is_null() && RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set_cache[i])) { + RD::get_singleton()->free(render_base_uniform_set_cache[i]); + } + render_base_uniform_set_cache[i] = RID(); } - render_base_uniform_set = RID(); } -void RenderForwardMobile::_update_render_base_uniform_set(const RendererRD::MaterialStorage::Samplers &p_samplers) { +void RenderForwardMobile::_update_render_base_uniform_set(const RendererRD::MaterialStorage::Samplers &p_samplers, BaseUniformSetCache p_cache_index) { RendererRD::LightStorage *light_storage = RendererRD::LightStorage::get_singleton(); - if (render_base_uniform_set.is_null() || !RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set) || (lightmap_texture_array_version != light_storage->lightmap_array_get_version())) { - if (render_base_uniform_set.is_valid() && RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set)) { - RD::get_singleton()->free(render_base_uniform_set); + if (render_base_uniform_set_cache[p_cache_index].is_null() || !RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set_cache[p_cache_index]) || (lightmap_texture_array_version_cache[p_cache_index] != light_storage->lightmap_array_get_version())) { + if (render_base_uniform_set_cache[p_cache_index].is_valid() && RD::get_singleton()->uniform_set_is_valid(render_base_uniform_set_cache[p_cache_index])) { + RD::get_singleton()->free(render_base_uniform_set_cache[p_cache_index]); } - // This is all loaded into set 0 - - lightmap_texture_array_version = light_storage->lightmap_array_get_version(); + lightmap_texture_array_version_cache[p_cache_index] = light_storage->lightmap_array_get_version(); Vector<RD::Uniform> uniforms; @@ -1699,8 +1708,9 @@ void RenderForwardMobile::_update_render_base_uniform_set(const RendererRD::Mate uniforms.append_array(p_samplers.get_uniforms(SAMPLERS_BINDING_FIRST_INDEX)); - render_base_uniform_set = RD::get_singleton()->uniform_set_create(uniforms, scene_shader.default_shader_rd, SCENE_UNIFORM_SET); + render_base_uniform_set_cache[p_cache_index] = RD::get_singleton()->uniform_set_create(uniforms, scene_shader.default_shader_rd, SCENE_UNIFORM_SET); } + render_base_uniform_set = render_base_uniform_set_cache[p_cache_index]; } RID RenderForwardMobile::_render_buffers_get_normal_texture(Ref<RenderSceneBuffersRD> p_render_buffers) { diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h index c8e42e2cf1..f10d3c1568 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h @@ -198,9 +198,19 @@ private: RID _setup_render_pass_uniform_set(RenderListType p_render_list, const RenderDataRD *p_render_data, RID p_radiance_texture, bool p_use_directional_shadow_atlas = false, int p_index = 0); void _pre_opaque_render(RenderDataRD *p_render_data); - uint64_t lightmap_texture_array_version = 0xFFFFFFFF; + enum BaseUniformSetCache { + BASE_UNIFORM_SET_CACHE_VIEWPORT, + BASE_UNIFORM_SET_CACHE_DEFAULT, + BASE_UNIFORM_SET_CACHE_MAX + }; + + // One for custom samplers, one for default samplers. + // Need to switch between them as default is needed for probes, shadows, materials, etc. + RID render_base_uniform_set_cache[BASE_UNIFORM_SET_CACHE_MAX]; + + uint64_t lightmap_texture_array_version_cache[BASE_UNIFORM_SET_CACHE_MAX] = { 0xFFFFFFFF, 0xFFFFFFFF }; - void _update_render_base_uniform_set(const RendererRD::MaterialStorage::Samplers &p_samplers); + void _update_render_base_uniform_set(const RendererRD::MaterialStorage::Samplers &p_samplers, BaseUniformSetCache p_cache_index); void _update_instance_data_buffer(RenderListType p_render_list); void _fill_instance_data(RenderListType p_render_list, uint32_t p_offset = 0, int32_t p_max_elements = -1, bool p_update_buffer = true); diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 620262f30e..43615f0d7e 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -635,7 +635,8 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint64_t p_format, uint // If using compression we store tangent while storing vertices. if (!(p_format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES)) { Variant::Type type = p_arrays[ai].get_type(); - ERR_FAIL_COND_V(type != Variant::PACKED_FLOAT32_ARRAY && type != Variant::PACKED_FLOAT64_ARRAY, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(type != Variant::PACKED_FLOAT32_ARRAY && type != Variant::PACKED_FLOAT64_ARRAY && type != Variant::NIL, ERR_INVALID_PARAMETER); + if (type == Variant::PACKED_FLOAT32_ARRAY) { Vector<float> array = p_arrays[ai]; ERR_FAIL_COND_V(array.size() != p_vertex_array_len * 4, ERR_INVALID_PARAMETER); @@ -657,7 +658,7 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint64_t p_format, uint memcpy(&vw[p_offsets[ai] + i * p_normal_stride], vector, 4); } - } else { // PACKED_FLOAT64_ARRAY + } else if (type == Variant::PACKED_FLOAT64_ARRAY) { Vector<double> array = p_arrays[ai]; ERR_FAIL_COND_V(array.size() != p_vertex_array_len * 4, ERR_INVALID_PARAMETER); const double *src_ptr = array.ptr(); @@ -678,6 +679,30 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint64_t p_format, uint memcpy(&vw[p_offsets[ai] + i * p_normal_stride], vector, 4); } + } else { // No tangent array. + ERR_FAIL_COND_V(p_arrays[RS::ARRAY_NORMAL].get_type() != Variant::PACKED_VECTOR3_ARRAY, ERR_INVALID_PARAMETER); + + Vector<Vector3> normal_array = p_arrays[RS::ARRAY_NORMAL]; + ERR_FAIL_COND_V(normal_array.size() != p_vertex_array_len, ERR_INVALID_PARAMETER); + const Vector3 *normal_src = normal_array.ptr(); + // Set data for tangent. + for (int i = 0; i < p_vertex_array_len; i++) { + // Generate an arbitrary vector that is tangential to normal. + Vector3 tan = Vector3(0.0, 1.0, 0.0).cross(normal_src[i].normalized()); + Vector2 res = tan.octahedron_tangent_encode(1.0); + uint16_t vector[2] = { + (uint16_t)CLAMP(res.x * 65535, 0, 65535), + (uint16_t)CLAMP(res.y * 65535, 0, 65535), + }; + + if (vector[0] == 0 && vector[1] == 65535) { + // (1, 1) and (0, 1) decode to the same value, but (0, 1) messes with our compression detection. + // So we sanitize here. + vector[0] = 65535; + } + + memcpy(&vw[p_offsets[ai] + i * p_normal_stride], vector, 4); + } } } } break; @@ -1172,6 +1197,11 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa } break; } ERR_FAIL_COND_V(array_len == 0, ERR_INVALID_DATA); + } else if (i == RS::ARRAY_NORMAL) { + if (p_arrays[RS::ARRAY_TANGENT].get_type() == Variant::NIL) { + // We must use tangents if using normals. + format |= (1ULL << RS::ARRAY_TANGENT); + } } else if (i == RS::ARRAY_BONES) { switch (p_arrays[i].get_type()) { case Variant::PACKED_INT32_ARRAY: { @@ -1242,11 +1272,6 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa ERR_FAIL_COND_V_MSG(!(format & RS::ARRAY_FORMAT_NORMAL), ERR_INVALID_PARAMETER, "Can't use compression flag 'ARRAY_FLAG_COMPRESS_ATTRIBUTES' while using tangents without normal array."); } - if ((format & RS::ARRAY_FLAG_COMPRESS_ATTRIBUTES) && !(format & RS::ARRAY_FORMAT_TANGENT)) { - // If no tangent array provided, we will generate one. - format |= RS::ARRAY_FORMAT_TANGENT; - } - int vertex_array_size = (vertex_element_size + normal_element_size) * array_len; int attrib_array_size = attrib_element_size * array_len; int skin_array_size = skin_element_size * array_len; diff --git a/tests/scene/test_viewport.h b/tests/scene/test_viewport.h index 0c53668c6d..1afae66ee0 100644 --- a/tests/scene/test_viewport.h +++ b/tests/scene/test_viewport.h @@ -50,17 +50,39 @@ protected: void _notification(int p_what) { switch (p_what) { case NOTIFICATION_MOUSE_ENTER: { + if (mouse_over) { + invalid_order = true; + } mouse_over = true; } break; case NOTIFICATION_MOUSE_EXIT: { + if (!mouse_over) { + invalid_order = true; + } mouse_over = false; } break; + + case NOTIFICATION_MOUSE_ENTER_SELF: { + if (mouse_over_self) { + invalid_order = true; + } + mouse_over_self = true; + } break; + + case NOTIFICATION_MOUSE_EXIT_SELF: { + if (!mouse_over_self) { + invalid_order = true; + } + mouse_over_self = false; + } break; } } public: bool mouse_over = false; + bool mouse_over_self = false; + bool invalid_order = false; }; // `NotificationControlViewport`-derived class that additionally @@ -119,12 +141,15 @@ public: TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { DragStart *node_a = memnew(DragStart); - Control *node_b = memnew(Control); + NotificationControlViewport *node_b = memnew(NotificationControlViewport); Node2D *node_c = memnew(Node2D); DragTarget *node_d = memnew(DragTarget); - Control *node_e = memnew(Control); + NotificationControlViewport *node_e = memnew(NotificationControlViewport); Node *node_f = memnew(Node); - Control *node_g = memnew(Control); + NotificationControlViewport *node_g = memnew(NotificationControlViewport); + NotificationControlViewport *node_h = memnew(NotificationControlViewport); + NotificationControlViewport *node_i = memnew(NotificationControlViewport); + NotificationControlViewport *node_j = memnew(NotificationControlViewport); node_a->set_name(SNAME("NodeA")); node_b->set_name(SNAME("NodeB")); @@ -133,6 +158,9 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_e->set_name(SNAME("NodeE")); node_f->set_name(SNAME("NodeF")); node_g->set_name(SNAME("NodeG")); + node_h->set_name(SNAME("NodeH")); + node_i->set_name(SNAME("NodeI")); + node_j->set_name(SNAME("NodeJ")); node_a->set_position(Point2i(0, 0)); node_b->set_position(Point2i(10, 10)); @@ -140,16 +168,25 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { node_d->set_position(Point2i(10, 10)); node_e->set_position(Point2i(10, 100)); node_g->set_position(Point2i(10, 100)); + node_h->set_position(Point2i(10, 120)); + node_i->set_position(Point2i(2, 0)); + node_j->set_position(Point2i(2, 0)); node_a->set_size(Point2i(30, 30)); node_b->set_size(Point2i(30, 30)); node_d->set_size(Point2i(30, 30)); node_e->set_size(Point2i(10, 10)); node_g->set_size(Point2i(10, 10)); + node_h->set_size(Point2i(10, 10)); + node_i->set_size(Point2i(10, 10)); + node_j->set_size(Point2i(10, 10)); node_a->set_focus_mode(Control::FOCUS_CLICK); node_b->set_focus_mode(Control::FOCUS_CLICK); node_d->set_focus_mode(Control::FOCUS_CLICK); node_e->set_focus_mode(Control::FOCUS_CLICK); node_g->set_focus_mode(Control::FOCUS_CLICK); + node_h->set_focus_mode(Control::FOCUS_CLICK); + node_i->set_focus_mode(Control::FOCUS_CLICK); + node_j->set_focus_mode(Control::FOCUS_CLICK); Window *root = SceneTree::get_singleton()->get_root(); DisplayServerMock *DS = (DisplayServerMock *)(DisplayServer::get_singleton()); @@ -162,6 +199,9 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { // - e (Control) // - f (Node) // - g (Control) + // - h (Control) + // - i (Control) + // - j (Control) root->add_child(node_a); root->add_child(node_b); node_b->add_child(node_c); @@ -169,12 +209,17 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { root->add_child(node_e); node_e->add_child(node_f); node_f->add_child(node_g); + root->add_child(node_h); + node_h->add_child(node_i); + node_i->add_child(node_j); Point2i on_a = Point2i(5, 5); Point2i on_b = Point2i(15, 15); Point2i on_d = Point2i(25, 25); Point2i on_e = Point2i(15, 105); Point2i on_g = Point2i(15, 105); + Point2i on_i = Point2i(13, 125); + Point2i on_j = Point2i(15, 125); Point2i on_background = Point2i(500, 500); Point2i on_outside = Point2i(-1, -1); @@ -419,26 +464,612 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { SUBCASE("[Viewport][GuiInputEvent] Mouse Motion") { // FIXME: Tooltips are not yet tested. They likely require an internal clock. - SUBCASE("[Viewport][GuiInputEvent] Mouse Motion changes the Control, that it is over.") { + SUBCASE("[Viewport][GuiInputEvent] Mouse Motion changes the Control that it is over.") { SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); CHECK_FALSE(node_a->mouse_over); + CHECK_FALSE(node_a->mouse_over_self); // Move over Control. SEND_GUI_MOUSE_MOTION_EVENT(on_a, MouseButtonMask::NONE, Key::NONE); CHECK(node_a->mouse_over); + CHECK(node_a->mouse_over_self); // No change. SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(1, 1), MouseButtonMask::NONE, Key::NONE); CHECK(node_a->mouse_over); + CHECK(node_a->mouse_over_self); // Move over other Control. SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE); CHECK_FALSE(node_a->mouse_over); + CHECK_FALSE(node_a->mouse_over_self); CHECK(node_d->mouse_over); + CHECK(node_d->mouse_over_self); - // Move to background + // Move to background. SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); CHECK_FALSE(node_d->mouse_over); + CHECK_FALSE(node_d->mouse_over_self); + + CHECK_FALSE(node_a->invalid_order); + CHECK_FALSE(node_d->invalid_order); + } + + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation.") { + node_d->set_mouse_filter(Control::MOUSE_FILTER_PASS); + node_g->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_b->mouse_over); + CHECK_FALSE(node_b->mouse_over_self); + CHECK_FALSE(node_d->mouse_over); + CHECK_FALSE(node_d->mouse_over_self); + + // Move to Control node_d. node_b receives mouse over since it is only separated by a CanvasItem. + SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE); + CHECK(node_b->mouse_over); + CHECK_FALSE(node_b->mouse_over_self); + CHECK(node_d->mouse_over); + CHECK(node_d->mouse_over_self); + + // Move to background. + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_b->mouse_over); + CHECK_FALSE(node_b->mouse_over_self); + CHECK_FALSE(node_d->mouse_over); + CHECK_FALSE(node_d->mouse_over_self); + + CHECK_FALSE(node_e->mouse_over); + CHECK_FALSE(node_e->mouse_over_self); + CHECK_FALSE(node_g->mouse_over); + CHECK_FALSE(node_g->mouse_over_self); + + // Move to Control node_g. node_g receives mouse over but node_e does not since it is separated by a non-CanvasItem. + SEND_GUI_MOUSE_MOTION_EVENT(on_g, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_e->mouse_over); + CHECK_FALSE(node_e->mouse_over_self); + CHECK(node_g->mouse_over); + CHECK(node_g->mouse_over_self); + + // Move to background. + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_e->mouse_over); + CHECK_FALSE(node_e->mouse_over_self); + CHECK_FALSE(node_g->mouse_over); + CHECK_FALSE(node_g->mouse_over_self); + + CHECK_FALSE(node_b->invalid_order); + CHECK_FALSE(node_d->invalid_order); + CHECK_FALSE(node_e->invalid_order); + CHECK_FALSE(node_g->invalid_order); + + node_d->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_g->set_mouse_filter(Control::MOUSE_FILTER_STOP); + } + + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation when moving into child.") { + SIGNAL_WATCH(node_i, SNAME("mouse_entered")); + SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + Array signal_args; + signal_args.push_back(Array()); + + node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + // Move to background. + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + + // Move to Control node_i. + SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE); + CHECK(node_i->mouse_over); + CHECK(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Move to child Control node_j. node_i should not receive any new Mouse Enter signals. + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Move to parent Control node_i. node_i should not receive any new Mouse Enter signals. + SEND_GUI_MOUSE_MOTION_EVENT(on_i, MouseButtonMask::NONE, Key::NONE); + CHECK(node_i->mouse_over); + CHECK(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Move to background. + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK(SNAME("mouse_exited"), signal_args); + + CHECK_FALSE(node_i->invalid_order); + CHECK_FALSE(node_j->invalid_order); + + node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); + + SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); + SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + } + + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with top level.") { + node_c->set_as_top_level(true); + node_i->set_as_top_level(true); + node_c->set_position(node_b->get_global_position()); + node_i->set_position(node_h->get_global_position()); + node_d->set_mouse_filter(Control::MOUSE_FILTER_PASS); + node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); + node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_b->mouse_over); + CHECK_FALSE(node_b->mouse_over_self); + CHECK_FALSE(node_d->mouse_over); + CHECK_FALSE(node_d->mouse_over_self); + + // Move to Control node_d. node_b does not receive mouse over since node_c is top level. + SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_b->mouse_over); + CHECK_FALSE(node_b->mouse_over_self); + CHECK(node_d->mouse_over); + CHECK(node_d->mouse_over_self); + + // Move to background. + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_b->mouse_over); + CHECK_FALSE(node_b->mouse_over_self); + CHECK_FALSE(node_d->mouse_over); + CHECK_FALSE(node_d->mouse_over_self); + + CHECK_FALSE(node_g->mouse_over); + CHECK_FALSE(node_g->mouse_over_self); + CHECK_FALSE(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + + // Move to Control node_j. node_h does not receive mouse over since node_i is top level. + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + + // Move to background. + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + + CHECK_FALSE(node_b->invalid_order); + CHECK_FALSE(node_d->invalid_order); + CHECK_FALSE(node_e->invalid_order); + CHECK_FALSE(node_h->invalid_order); + CHECK_FALSE(node_i->invalid_order); + CHECK_FALSE(node_j->invalid_order); + + node_c->set_as_top_level(false); + node_i->set_as_top_level(false); + node_c->set_position(Point2i(0, 0)); + node_i->set_position(Point2i(0, 0)); + node_d->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); + } + + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with mouse filter stop.") { + node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + // Move to background. + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + + // Move to Control node_j. node_h does not receive mouse over since node_i is MOUSE_FILTER_STOP. + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + + // Move to background. + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + + CHECK_FALSE(node_h->invalid_order); + CHECK_FALSE(node_i->invalid_order); + CHECK_FALSE(node_j->invalid_order); + + node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); + } + + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification propagation with mouse filter ignore.") { + node_i->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + // Move to background. + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + + // Move to Control node_j. node_i does not receive mouse over since node_i is MOUSE_FILTER_IGNORE. + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + + // Move to background. + SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE); + CHECK_FALSE(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + + CHECK_FALSE(node_h->invalid_order); + CHECK_FALSE(node_i->invalid_order); + CHECK_FALSE(node_j->invalid_order); + + node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); + } + + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing top level.") { + SIGNAL_WATCH(node_i, SNAME("mouse_entered")); + SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + Array signal_args; + signal_args.push_back(Array()); + + node_d->set_mouse_filter(Control::MOUSE_FILTER_PASS); + node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); + node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + // Move to Control node_d. + SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE); + CHECK(node_b->mouse_over); + CHECK_FALSE(node_b->mouse_over_self); + CHECK(node_d->mouse_over); + CHECK(node_d->mouse_over_self); + + // Change node_c to be top level. node_b should receive Mouse Exit. + node_c->set_as_top_level(true); + CHECK_FALSE(node_b->mouse_over); + CHECK_FALSE(node_b->mouse_over_self); + CHECK(node_d->mouse_over); + CHECK(node_d->mouse_over_self); + + // Change node_c to be not top level. node_b should receive Mouse Enter. + node_c->set_as_top_level(false); + CHECK(node_b->mouse_over); + CHECK_FALSE(node_b->mouse_over_self); + CHECK(node_d->mouse_over); + CHECK(node_d->mouse_over_self); + + // Move to Control node_j. + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Change node_i to top level. node_h should receive Mouse Exit. node_i should not receive any new signals. + node_i->set_as_top_level(true); + CHECK_FALSE(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Change node_i to not top level. node_h should receive Mouse Enter. node_i should not receive any new signals. + node_i->set_as_top_level(false); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + CHECK_FALSE(node_b->invalid_order); + CHECK_FALSE(node_d->invalid_order); + CHECK_FALSE(node_e->invalid_order); + CHECK_FALSE(node_h->invalid_order); + CHECK_FALSE(node_i->invalid_order); + CHECK_FALSE(node_j->invalid_order); + + node_d->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); + + SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); + SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + } + + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing the mouse filter to stop.") { + SIGNAL_WATCH(node_i, SNAME("mouse_entered")); + SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + Array signal_args; + signal_args.push_back(Array()); + + node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); + node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + // Move to Control node_j. + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Change node_i to MOUSE_FILTER_STOP. node_h should receive Mouse Exit. node_i should not receive any new signals. + node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); + CHECK_FALSE(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Change node_i to MOUSE_FILTER_PASS. node_h should receive Mouse Enter. node_i should not receive any new signals. + node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + CHECK_FALSE(node_h->invalid_order); + CHECK_FALSE(node_i->invalid_order); + CHECK_FALSE(node_j->invalid_order); + + node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); + + SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); + SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + } + + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when changing the mouse filter to ignore.") { + SIGNAL_WATCH(node_i, SNAME("mouse_entered")); + SIGNAL_WATCH(node_i, SNAME("mouse_exited")); + Array signal_args; + signal_args.push_back(Array()); + + node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); + node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + // Move to Control node_j. + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Change node_i to MOUSE_FILTER_IGNORE. node_i should receive Mouse Exit. + node_i->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK(SNAME("mouse_exited"), signal_args); + + // Change node_i to MOUSE_FILTER_PASS. node_i should receive Mouse Enter. + node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Change node_j to MOUSE_FILTER_IGNORE. After updating the mouse motion, node_i should now have mouse_over_self. + node_j->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Change node_j to MOUSE_FILTER_PASS. After updating the mouse motion, node_j should now have mouse_over_self. + node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + CHECK_FALSE(node_h->invalid_order); + CHECK_FALSE(node_i->invalid_order); + CHECK_FALSE(node_j->invalid_order); + + node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); + + SIGNAL_UNWATCH(node_i, SNAME("mouse_entered")); + SIGNAL_UNWATCH(node_i, SNAME("mouse_exited")); + } + + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when removing the hovered Control.") { + SIGNAL_WATCH(node_h, SNAME("mouse_entered")); + SIGNAL_WATCH(node_h, SNAME("mouse_exited")); + Array signal_args; + signal_args.push_back(Array()); + + node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); + node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + // Move to Control node_j. + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Remove node_i from the tree. node_i and node_j should receive Mouse Exit. node_h should not receive any new signals. + node_h->remove_child(node_i); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Add node_i to the tree and update the mouse. node_i and node_j should receive Mouse Enter. node_h should not receive any new signals. + node_h->add_child(node_i); + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + CHECK_FALSE(node_h->invalid_order); + CHECK_FALSE(node_i->invalid_order); + CHECK_FALSE(node_j->invalid_order); + + node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); + + SIGNAL_UNWATCH(node_h, SNAME("mouse_entered")); + SIGNAL_UNWATCH(node_h, SNAME("mouse_exited")); + } + + SUBCASE("[Viewport][GuiInputEvent] Mouse Enter/Exit notification when hiding the hovered Control.") { + SIGNAL_WATCH(node_h, SNAME("mouse_entered")); + SIGNAL_WATCH(node_h, SNAME("mouse_exited")); + Array signal_args; + signal_args.push_back(Array()); + + node_i->set_mouse_filter(Control::MOUSE_FILTER_PASS); + node_j->set_mouse_filter(Control::MOUSE_FILTER_PASS); + + // Move to Control node_j. + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK(SNAME("mouse_entered"), signal_args); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Hide node_i. node_i and node_j should receive Mouse Exit. node_h should not receive any new signals. + node_i->hide(); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK_FALSE(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK_FALSE(node_j->mouse_over); + CHECK_FALSE(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + // Show node_i and update the mouse. node_i and node_j should receive Mouse Enter. node_h should not receive any new signals. + node_i->show(); + SEND_GUI_MOUSE_MOTION_EVENT(on_j, MouseButtonMask::NONE, Key::NONE); + CHECK(node_h->mouse_over); + CHECK_FALSE(node_h->mouse_over_self); + CHECK(node_i->mouse_over); + CHECK_FALSE(node_i->mouse_over_self); + CHECK(node_j->mouse_over); + CHECK(node_j->mouse_over_self); + SIGNAL_CHECK_FALSE(SNAME("mouse_entered")); + SIGNAL_CHECK_FALSE(SNAME("mouse_exited")); + + CHECK_FALSE(node_h->invalid_order); + CHECK_FALSE(node_i->invalid_order); + CHECK_FALSE(node_j->invalid_order); + + node_i->set_mouse_filter(Control::MOUSE_FILTER_STOP); + node_j->set_mouse_filter(Control::MOUSE_FILTER_STOP); + + SIGNAL_UNWATCH(node_h, SNAME("mouse_entered")); + SIGNAL_UNWATCH(node_h, SNAME("mouse_exited")); } SUBCASE("[Viewport][GuiInputEvent] Window Mouse Enter/Exit signals.") { @@ -710,6 +1341,9 @@ TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") { } } + memdelete(node_j); + memdelete(node_i); + memdelete(node_h); memdelete(node_g); memdelete(node_f); memdelete(node_e); |
