diff options
94 files changed, 1591 insertions, 596 deletions
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 0c765fe23c..ea75a6b079 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -331,6 +331,10 @@ static const _BuiltinActionDisplayName _builtin_action_display_names[] = { { "ui_text_caret_document_start.macos", TTRC("Caret Document Start") }, { "ui_text_caret_document_end", TTRC("Caret Document End") }, { "ui_text_caret_document_end.macos", TTRC("Caret Document End") }, + { "ui_text_caret_add_below", TTRC("Caret Add Below") }, + { "ui_text_caret_add_below.macos", TTRC("Caret Add Below") }, + { "ui_text_caret_add_above", TTRC("Caret Add Above") }, + { "ui_text_caret_add_above.macos", TTRC("Caret Add Above") }, { "ui_text_scroll_up", TTRC("Scroll Up") }, { "ui_text_scroll_up.macos", TTRC("Scroll Up") }, { "ui_text_scroll_down", TTRC("Scroll Down") }, @@ -617,6 +621,24 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL)); default_builtin_cache.insert("ui_text_caret_document_end.macos", inputs); + // Text Caret Addition Below/Above + + inputs = List<Ref<InputEvent>>(); + inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL)); + default_builtin_cache.insert("ui_text_caret_add_below", inputs); + + inputs = List<Ref<InputEvent>>(); + inputs.push_back(InputEventKey::create_reference(Key::L | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL)); + default_builtin_cache.insert("ui_text_caret_add_below.macos", inputs); + + inputs = List<Ref<InputEvent>>(); + inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL)); + default_builtin_cache.insert("ui_text_caret_add_above", inputs); + + inputs = List<Ref<InputEvent>>(); + inputs.push_back(InputEventKey::create_reference(Key::O | KeyModifierMask::SHIFT | KeyModifierMask::CMD_OR_CTRL)); + default_builtin_cache.insert("ui_text_caret_add_above.macos", inputs); + // Text Scrolling inputs = List<Ref<InputEvent>>(); diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 79e7fa16e3..7eb50d2261 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -351,7 +351,7 @@ Error DirAccess::copy(String p_from, String p_to, int p_chmod_flags) { const size_t copy_buffer_limit = 65536; // 64 KB fsrc->seek_end(0); - int size = fsrc->get_position(); + uint64_t size = fsrc->get_position(); fsrc->seek(0); err = OK; size_t buffer_size = MIN(size * sizeof(uint8_t), copy_buffer_limit); diff --git a/core/object/object.h b/core/object/object.h index 359ab0f211..fa3003cc1f 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -133,7 +133,7 @@ enum PropertyUsageFlags { PROPERTY_USAGE_ARRAY = 1 << 29, // Used in the inspector to group properties as elements of an array. PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR, - PROPERTY_USAGE_DEFAULT_INTL = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_INTERNATIONALIZED, + PROPERTY_USAGE_DEFAULT_INTL = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNATIONALIZED, PROPERTY_USAGE_NO_EDITOR = PROPERTY_USAGE_STORAGE, }; diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml index ca482a39e0..4c62ab2a7f 100644 --- a/doc/classes/CodeEdit.xml +++ b/doc/classes/CodeEdit.xml @@ -616,8 +616,8 @@ <theme_item name="font_readonly_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)"> Sets the font [Color] when [member TextEdit.editable] is disabled. </theme_item> - <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> - Sets the [Color] of the selected text. [member TextEdit.override_selected_font_color] has to be enabled. + <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)"> + Sets the [Color] of the selected text. If equal to [code]Color(0, 0, 0, 0)[/code], it will be ignored. </theme_item> <theme_item name="line_length_guideline_color" data_type="color" type="Color" default="Color(0.3, 0.5, 0.8, 0.1)"> [Color] of the main line length guideline, secondary guidelines will have 50% alpha applied. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 8da73f1b7d..925d4ec7c4 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -856,6 +856,18 @@ <member name="input/ui_text_backspace_word.macos" type="Dictionary" setter="" getter=""> macOS specific override for the shortcut to delete a word. </member> + <member name="input/ui_text_caret_add_above" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to add an additional caret above every caret of a text + </member> + <member name="input/ui_text_caret_add_above.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to add a caret above every caret + </member> + <member name="input/ui_text_caret_add_below" type="Dictionary" setter="" getter=""> + Default [InputEventAction] to add an additional caret below every caret of a text + </member> + <member name="input/ui_text_caret_add_below.macos" type="Dictionary" setter="" getter=""> + macOS specific override for the shortcut to add a caret below every caret + </member> <member name="input/ui_text_caret_document_end" type="Dictionary" setter="" getter=""> Default [InputEventAction] to move the text cursor the the end of the text. [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index b5a917b2bb..760861272f 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -479,9 +479,6 @@ <member name="meta_underlined" type="bool" setter="set_meta_underline" getter="is_meta_underlined" default="true"> If [code]true[/code], the label underlines meta tags such as [code][url]{text}[/url][/code]. </member> - <member name="override_selected_font_color" type="bool" setter="set_override_selected_font_color" getter="is_overriding_selected_font_color" default="false"> - If [code]true[/code], the label uses the custom font color. - </member> <member name="progress_bar_delay" type="int" setter="set_progress_bar_delay" getter="get_progress_bar_delay" default="1000"> The delay after which the loading progress bar is displayed, in milliseconds. Set to [code]-1[/code] to disable progress bar entirely. [b]Note:[/b] Progress bar is displayed only if [member threaded] is enabled. @@ -629,8 +626,8 @@ <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> The default tint of text outline. </theme_item> - <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(0, 0, 0, 1)"> - The color of selected text, used when [member selection_enabled] is [code]true[/code]. + <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)"> + The color of selected text, used when [member selection_enabled] is [code]true[/code]. If equal to [code]Color(0, 0, 0, 0)[/code], it will be ignored. </theme_item> <theme_item name="font_shadow_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)"> The color of the font's shadow. diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 879a355f25..d4f5233438 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -63,6 +63,13 @@ Adds a new caret at the given location. Returns the index of the new caret, or [code]-1[/code] if the location is invalid. </description> </method> + <method name="add_caret_at_carets"> + <return type="void" /> + <param index="0" name="below" type="bool" /> + <description> + Adds an additional caret above or below every caret. If [param below] is true the new caret will be added below and above otherwise. + </description> + </method> <method name="add_gutter"> <return type="void" /> <param index="0" name="at" type="int" default="-1" /> @@ -1103,9 +1110,6 @@ The width, in pixels, of the minimap. </member> <member name="mouse_default_cursor_shape" type="int" setter="set_default_cursor_shape" getter="get_default_cursor_shape" overrides="Control" enum="Control.CursorShape" default="1" /> - <member name="override_selected_font_color" type="bool" setter="set_override_selected_font_color" getter="is_overriding_selected_font_color" default="false"> - If [code]true[/code], custom [code]font_selected_color[/code] will be used for selected text. - </member> <member name="placeholder_text" type="String" setter="set_placeholder" getter="get_placeholder" default=""""> Text shown when the [TextEdit] is empty. It is [b]not[/b] the [TextEdit]'s default value (see [member text]). </member> @@ -1369,8 +1373,8 @@ <theme_item name="font_readonly_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)"> Sets the font [Color] when [member editable] is disabled. </theme_item> - <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> - Sets the [Color] of the selected text. [member override_selected_font_color] has to be enabled. + <theme_item name="font_selected_color" data_type="color" type="Color" default="Color(0, 0, 0, 0)"> + Sets the [Color] of the selected text. If equal to [code]Color(0, 0, 0, 0)[/code], it will be ignored. </theme_item> <theme_item name="search_result_border_color" data_type="color" type="Color" default="Color(0.3, 0.3, 0.3, 0.4)"> [Color] of the border around text that matches the search query. diff --git a/doc/classes/float.xml b/doc/classes/float.xml index d7232bb0e9..e3938fb5d5 100644 --- a/doc/classes/float.xml +++ b/doc/classes/float.xml @@ -114,12 +114,17 @@ <return type="Vector4" /> <param index="0" name="right" type="Vector4" /> <description> + Multiplies each component of the [Vector4] by the given [float]. </description> </operator> <operator name="operator *"> <return type="Vector4" /> <param index="0" name="right" type="Vector4i" /> <description> + Multiplies each component of the [Vector4i] by the given [float]. Returns a [Vector4]. + [codeblock] + print(0.9 * Vector4i(10, 15, 20, -10)) # Prints "(9, 13.5, 18, -9)" + [/codeblock] </description> </operator> <operator name="operator *"> @@ -140,12 +145,20 @@ <return type="float" /> <param index="0" name="right" type="float" /> <description> + Raises a [float] to a power of a [float]. + [codeblock] + print(39.0625**0.25) # 2.5 + [/codeblock] </description> </operator> <operator name="operator **"> <return type="float" /> <param index="0" name="right" type="int" /> <description> + Raises a [float] to a power of an [int]. The result is a [float]. + [codeblock] + print(0.9**3) # 0.729 + [/codeblock] </description> </operator> <operator name="operator +"> diff --git a/doc/classes/int.xml b/doc/classes/int.xml index 868a8e9944..689cb7fe8e 100644 --- a/doc/classes/int.xml +++ b/doc/classes/int.xml @@ -161,12 +161,14 @@ <return type="Vector4" /> <param index="0" name="right" type="Vector4" /> <description> + Multiplies each component of the [Vector4] by the given [int]. </description> </operator> <operator name="operator *"> <return type="Vector4i" /> <param index="0" name="right" type="Vector4i" /> <description> + Multiplies each component of the [Vector4i] by the given [int]. </description> </operator> <operator name="operator *"> @@ -187,12 +189,20 @@ <return type="float" /> <param index="0" name="right" type="float" /> <description> + Raises an [int] to a power of a [float]. The result is a [float]. + [codeblock] + print(8**0.25) # 1.68179283050743 + [/codeblock] </description> </operator> <operator name="operator **"> <return type="int" /> <param index="0" name="right" type="int" /> <description> + Raises an [int] to a power of a [int]. + [codeblock] + print(5**5) # 3125 + [/codeblock] </description> </operator> <operator name="operator +"> diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp index ea97dff522..b2d01b02fb 100644 --- a/drivers/gles3/rasterizer_gles3.cpp +++ b/drivers/gles3/rasterizer_gles3.cpp @@ -101,8 +101,7 @@ void RasterizerGLES3::begin_frame(double frame_step) { scene->set_time(time_total, frame_step); GLES3::Utilities *utils = GLES3::Utilities::get_singleton(); - utils->info.render_final = utils->info.render; - utils->info.render.reset(); + utils->_capture_timestamps_begin(); //scene->iteration(); } @@ -272,6 +271,13 @@ RasterizerGLES3::~RasterizerGLES3() { } void RasterizerGLES3::prepare_for_blitting_render_targets() { + // This is a hack, but this function is called one time after all viewports have been updated. + // So it marks the end of the frame for all viewports + // In the OpenGL renderer we have to call end_frame for each viewport so we can swap the + // buffers for each window before proceeding to the next. + // This allows us to only increment the frame after all viewports are done. + GLES3::Utilities *utils = GLES3::Utilities::get_singleton(); + utils->capture_timestamps_end(); } void RasterizerGLES3::_blit_render_target_to_screen(RID p_render_target, DisplayServer::WindowID p_screen, const Rect2 &p_screen_rect, uint32_t p_layer) { diff --git a/drivers/gles3/storage/utilities.cpp b/drivers/gles3/storage/utilities.cpp index 0a04f0d4d0..a2d1ba376a 100644 --- a/drivers/gles3/storage/utilities.cpp +++ b/drivers/gles3/storage/utilities.cpp @@ -38,16 +38,35 @@ #include "particles_storage.h" #include "texture_storage.h" +#include "servers/rendering/rendering_server_globals.h" + using namespace GLES3; Utilities *Utilities::singleton = nullptr; Utilities::Utilities() { singleton = this; + frame = 0; + for (int i = 0; i < FRAME_COUNT; i++) { + frames[i].index = 0; + glGenQueries(max_timestamp_query_elements, frames[i].queries); + + frames[i].timestamp_names.resize(max_timestamp_query_elements); + frames[i].timestamp_cpu_values.resize(max_timestamp_query_elements); + frames[i].timestamp_count = 0; + + frames[i].timestamp_result_names.resize(max_timestamp_query_elements); + frames[i].timestamp_cpu_result_values.resize(max_timestamp_query_elements); + frames[i].timestamp_result_values.resize(max_timestamp_query_elements); + frames[i].timestamp_result_count = 0; + } } Utilities::~Utilities() { singleton = nullptr; + for (int i = 0; i < FRAME_COUNT; i++) { + glDeleteQueries(max_timestamp_query_elements, frames[i].queries); + } } Vector<uint8_t> Utilities::buffer_get_data(GLenum p_target, GLuint p_buffer, uint32_t p_buffer_size) { @@ -213,87 +232,69 @@ void Utilities::visibility_notifier_call(RID p_notifier, bool p_enter, bool p_de /* TIMING */ -//void Utilities::render_info_begin_capture() { -// info.snap = info.render; -//} - -//void Utilities::render_info_end_capture() { -// info.snap.object_count = info.render.object_count - info.snap.object_count; -// info.snap.draw_call_count = info.render.draw_call_count - info.snap.draw_call_count; -// info.snap.material_switch_count = info.render.material_switch_count - info.snap.material_switch_count; -// info.snap.surface_switch_count = info.render.surface_switch_count - info.snap.surface_switch_count; -// info.snap.shader_rebind_count = info.render.shader_rebind_count - info.snap.shader_rebind_count; -// info.snap.vertices_count = info.render.vertices_count - info.snap.vertices_count; -// info.snap._2d_item_count = info.render._2d_item_count - info.snap._2d_item_count; -// info.snap._2d_draw_call_count = info.render._2d_draw_call_count - info.snap._2d_draw_call_count; -//} - -//int Utilities::get_captured_render_info(RS::RenderInfo p_info) { -// switch (p_info) { -// case RS::INFO_OBJECTS_IN_FRAME: { -// return info.snap.object_count; -// } break; -// case RS::INFO_VERTICES_IN_FRAME: { -// return info.snap.vertices_count; -// } break; -// case RS::INFO_MATERIAL_CHANGES_IN_FRAME: { -// return info.snap.material_switch_count; -// } break; -// case RS::INFO_SHADER_CHANGES_IN_FRAME: { -// return info.snap.shader_rebind_count; -// } break; -// case RS::INFO_SURFACE_CHANGES_IN_FRAME: { -// return info.snap.surface_switch_count; -// } break; -// case RS::INFO_DRAW_CALLS_IN_FRAME: { -// return info.snap.draw_call_count; -// } break; -// /* -// case RS::INFO_2D_ITEMS_IN_FRAME: { -// return info.snap._2d_item_count; -// } break; -// case RS::INFO_2D_DRAW_CALLS_IN_FRAME: { -// return info.snap._2d_draw_call_count; -// } break; -// */ -// default: { -// return get_render_info(p_info); -// } -// } -//} - -//int Utilities::get_render_info(RS::RenderInfo p_info) { -// switch (p_info) { -// case RS::INFO_OBJECTS_IN_FRAME: -// return info.render_final.object_count; -// case RS::INFO_VERTICES_IN_FRAME: -// return info.render_final.vertices_count; -// case RS::INFO_MATERIAL_CHANGES_IN_FRAME: -// return info.render_final.material_switch_count; -// case RS::INFO_SHADER_CHANGES_IN_FRAME: -// return info.render_final.shader_rebind_count; -// case RS::INFO_SURFACE_CHANGES_IN_FRAME: -// return info.render_final.surface_switch_count; -// case RS::INFO_DRAW_CALLS_IN_FRAME: -// return info.render_final.draw_call_count; -// /* -// case RS::INFO_2D_ITEMS_IN_FRAME: -// return info.render_final._2d_item_count; -// case RS::INFO_2D_DRAW_CALLS_IN_FRAME: -// return info.render_final._2d_draw_call_count; -//*/ -// case RS::INFO_USAGE_VIDEO_MEM_TOTAL: -// return 0; //no idea -// case RS::INFO_VIDEO_MEM_USED: -// return info.vertex_mem + info.texture_mem; -// case RS::INFO_TEXTURE_MEM_USED: -// return info.texture_mem; -// case RS::INFO_VERTEX_MEM_USED: -// return info.vertex_mem; -// default: -// return 0; //no idea either -// } -//} +void Utilities::capture_timestamps_begin() { + capture_timestamp("Frame Begin"); +} + +void Utilities::capture_timestamp(const String &p_name) { + ERR_FAIL_COND(frames[frame].timestamp_count >= max_timestamp_query_elements); + +#ifdef GLES_OVER_GL + glQueryCounter(frames[frame].queries[frames[frame].timestamp_count], GL_TIMESTAMP); +#endif + + frames[frame].timestamp_names[frames[frame].timestamp_count] = p_name; + frames[frame].timestamp_cpu_values[frames[frame].timestamp_count] = OS::get_singleton()->get_ticks_usec(); + frames[frame].timestamp_count++; +} + +void Utilities::_capture_timestamps_begin() { + // frame is incremented at the end of the frame so this gives us the queries for frame - 2. By then they should be ready. + if (frames[frame].timestamp_count) { +#ifdef GLES_OVER_GL + for (uint32_t i = 0; i < frames[frame].timestamp_count; i++) { + uint64_t temp = 0; + glGetQueryObjectui64v(frames[frame].queries[i], GL_QUERY_RESULT, &temp); + frames[frame].timestamp_result_values[i] = temp; + } +#endif + SWAP(frames[frame].timestamp_names, frames[frame].timestamp_result_names); + SWAP(frames[frame].timestamp_cpu_values, frames[frame].timestamp_cpu_result_values); + } + + frames[frame].timestamp_result_count = frames[frame].timestamp_count; + frames[frame].timestamp_count = 0; + frames[frame].index = Engine::get_singleton()->get_frames_drawn(); + capture_timestamp("Internal Begin"); +} + +void Utilities::capture_timestamps_end() { + capture_timestamp("Internal End"); + frame = (frame + 1) % FRAME_COUNT; +} + +uint32_t Utilities::get_captured_timestamps_count() const { + return frames[frame].timestamp_result_count; +} + +uint64_t Utilities::get_captured_timestamps_frame() const { + return frames[frame].index; +} + +uint64_t Utilities::get_captured_timestamp_gpu_time(uint32_t p_index) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_index, frames[frame].timestamp_result_count, 0); + return frames[frame].timestamp_result_values[p_index]; +} + +uint64_t Utilities::get_captured_timestamp_cpu_time(uint32_t p_index) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_index, frames[frame].timestamp_result_count, 0); + return frames[frame].timestamp_cpu_result_values[p_index]; +} + +String Utilities::get_captured_timestamp_name(uint32_t p_index) const { + ERR_FAIL_UNSIGNED_INDEX_V(p_index, frames[frame].timestamp_result_count, String()); + return frames[frame].timestamp_result_names[p_index]; +} /* MISC */ diff --git a/drivers/gles3/storage/utilities.h b/drivers/gles3/storage/utilities.h index 936ac35cc9..55a875958e 100644 --- a/drivers/gles3/storage/utilities.h +++ b/drivers/gles3/storage/utilities.h @@ -79,62 +79,35 @@ public: /* TIMING */ - struct Info { - uint64_t texture_mem = 0; - uint64_t vertex_mem = 0; - - struct Render { - uint32_t object_count; - uint32_t draw_call_count; - uint32_t material_switch_count; - uint32_t surface_switch_count; - uint32_t shader_rebind_count; - uint32_t vertices_count; - uint32_t _2d_item_count; - uint32_t _2d_draw_call_count; - - void reset() { - object_count = 0; - draw_call_count = 0; - material_switch_count = 0; - surface_switch_count = 0; - shader_rebind_count = 0; - vertices_count = 0; - _2d_item_count = 0; - _2d_draw_call_count = 0; - } - } render, render_final, snap; - - Info() { - render.reset(); - render_final.reset(); - } - - } info; - - virtual void capture_timestamps_begin() override {} - virtual void capture_timestamp(const String &p_name) override {} - virtual uint32_t get_captured_timestamps_count() const override { - return 0; - } - virtual uint64_t get_captured_timestamps_frame() const override { - return 0; - } - virtual uint64_t get_captured_timestamp_gpu_time(uint32_t p_index) const override { - return 0; - } - virtual uint64_t get_captured_timestamp_cpu_time(uint32_t p_index) const override { - return 0; - } - virtual String get_captured_timestamp_name(uint32_t p_index) const override { - return String(); - } - - // void render_info_begin_capture() override; - // void render_info_end_capture() override; - // int get_captured_render_info(RS::RenderInfo p_info) override; - - // int get_render_info(RS::RenderInfo p_info) override; +#define MAX_QUERIES 256 +#define FRAME_COUNT 3 + + struct Frame { + GLuint queries[MAX_QUERIES]; + TightLocalVector<String> timestamp_names; + TightLocalVector<uint64_t> timestamp_cpu_values; + uint32_t timestamp_count = 0; + TightLocalVector<String> timestamp_result_names; + TightLocalVector<uint64_t> timestamp_cpu_result_values; + TightLocalVector<uint64_t> timestamp_result_values; + uint32_t timestamp_result_count = 0; + uint64_t index = 0; + }; + + const uint32_t max_timestamp_query_elements = MAX_QUERIES; + + Frame frames[FRAME_COUNT]; // Frames for capturing timestamps. We use 3 so we don't need to wait for commands to complete + uint32_t frame = 0; + + virtual void capture_timestamps_begin() override; + virtual void capture_timestamp(const String &p_name) override; + virtual uint32_t get_captured_timestamps_count() const override; + virtual uint64_t get_captured_timestamps_frame() const override; + virtual uint64_t get_captured_timestamp_gpu_time(uint32_t p_index) const override; + virtual uint64_t get_captured_timestamp_cpu_time(uint32_t p_index) const override; + virtual String get_captured_timestamp_name(uint32_t p_index) const override; + void _capture_timestamps_begin(); + void capture_timestamps_end(); /* MISC */ diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp index 9ee5a67471..56e97b742c 100644 --- a/drivers/vulkan/vulkan_context.cpp +++ b/drivers/vulkan/vulkan_context.cpp @@ -687,21 +687,43 @@ Error VulkanContext::_check_capabilities() { vkGetPhysicalDeviceFeatures2_func(gpu, &device_features); - vrs_capabilities.pipeline_vrs_supported = vrs_features.pipelineFragmentShadingRate; - vrs_capabilities.primitive_vrs_supported = vrs_features.primitiveFragmentShadingRate; - vrs_capabilities.attachment_vrs_supported = vrs_features.attachmentFragmentShadingRate; + // We must check that the relative extension is present before assuming a + // feature as enabled. Actually, according to the spec we shouldn't add the + // structs in pNext at all, but this works fine. + // See also: https://github.com/godotengine/godot/issues/65409 + for (uint32_t i = 0; i < enabled_extension_count; ++i) { + if (!strcmp(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, extension_names[i])) { + vrs_capabilities.pipeline_vrs_supported = vrs_features.pipelineFragmentShadingRate; + vrs_capabilities.primitive_vrs_supported = vrs_features.primitiveFragmentShadingRate; + vrs_capabilities.attachment_vrs_supported = vrs_features.attachmentFragmentShadingRate; - multiview_capabilities.is_supported = multiview_features.multiview; - multiview_capabilities.geometry_shader_is_supported = multiview_features.multiviewGeometryShader; - multiview_capabilities.tessellation_shader_is_supported = multiview_features.multiviewTessellationShader; + continue; + } - shader_capabilities.shader_float16_is_supported = shader_features.shaderFloat16; - shader_capabilities.shader_int8_is_supported = shader_features.shaderInt8; + if (!strcmp(VK_KHR_MULTIVIEW_EXTENSION_NAME, extension_names[i])) { + multiview_capabilities.is_supported = multiview_features.multiview; + multiview_capabilities.geometry_shader_is_supported = multiview_features.multiviewGeometryShader; + multiview_capabilities.tessellation_shader_is_supported = multiview_features.multiviewTessellationShader; - storage_buffer_capabilities.storage_buffer_16_bit_access_is_supported = storage_feature.storageBuffer16BitAccess; - storage_buffer_capabilities.uniform_and_storage_buffer_16_bit_access_is_supported = storage_feature.uniformAndStorageBuffer16BitAccess; - storage_buffer_capabilities.storage_push_constant_16_is_supported = storage_feature.storagePushConstant16; - storage_buffer_capabilities.storage_input_output_16 = storage_feature.storageInputOutput16; + continue; + } + + if (!strcmp(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, extension_names[i])) { + shader_capabilities.shader_float16_is_supported = shader_features.shaderFloat16; + shader_capabilities.shader_int8_is_supported = shader_features.shaderInt8; + + continue; + } + + if (!strcmp(VK_KHR_16BIT_STORAGE_EXTENSION_NAME, extension_names[i])) { + storage_buffer_capabilities.storage_buffer_16_bit_access_is_supported = storage_feature.storageBuffer16BitAccess; + storage_buffer_capabilities.uniform_and_storage_buffer_16_bit_access_is_supported = storage_feature.uniformAndStorageBuffer16BitAccess; + storage_buffer_capabilities.storage_push_constant_16_is_supported = storage_feature.storagePushConstant16; + storage_buffer_capabilities.storage_input_output_16 = storage_feature.storageInputOutput16; + + continue; + } + } } // Check extended properties. diff --git a/editor/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index 06b73cf296..ecd8fcb2ba 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -77,6 +77,7 @@ EditorDebuggerNode::EditorDebuggerNode() { remote_scene_tree = memnew(EditorDebuggerTree); remote_scene_tree->connect("object_selected", callable_mp(this, &EditorDebuggerNode::_remote_object_requested)); remote_scene_tree->connect("save_node", callable_mp(this, &EditorDebuggerNode::_save_node_requested)); + remote_scene_tree->connect("button_clicked", callable_mp(this, &EditorDebuggerNode::_remote_tree_button_pressed)); SceneTreeDock::get_singleton()->add_remote_tree_editor(remote_scene_tree); SceneTreeDock::get_singleton()->connect("remote_tree_selected", callable_mp(this, &EditorDebuggerNode::request_remote_tree)); @@ -573,6 +574,24 @@ void EditorDebuggerNode::_remote_tree_updated(int p_debugger) { remote_scene_tree->update_scene_tree(get_current_debugger()->get_remote_tree(), p_debugger); } +void EditorDebuggerNode::_remote_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) { + if (p_button != MouseButton::LEFT) { + return; + } + + TreeItem *item = Object::cast_to<TreeItem>(p_item); + ERR_FAIL_COND(!item); + + if (p_id == EditorDebuggerTree::BUTTON_SUBSCENE) { + remote_scene_tree->emit_signal(SNAME("open"), item->get_meta("scene_file_path")); + } else if (p_id == EditorDebuggerTree::BUTTON_VISIBILITY) { + ObjectID obj_id = item->get_metadata(0); + ERR_FAIL_COND(obj_id.is_null()); + get_current_debugger()->update_remote_object(obj_id, "visible", !item->get_meta("visible")); + get_current_debugger()->request_remote_tree(); + } +} + void EditorDebuggerNode::_remote_object_updated(ObjectID p_id, int p_debugger) { if (p_debugger != tabs->get_current_tab()) { return; diff --git a/editor/debugger/editor_debugger_node.h b/editor/debugger/editor_debugger_node.h index e79e60b180..305f18a652 100644 --- a/editor/debugger/editor_debugger_node.h +++ b/editor/debugger/editor_debugger_node.h @@ -129,6 +129,7 @@ protected: void _debugger_wants_stop(int p_id); void _debugger_changed(int p_tab); void _remote_tree_updated(int p_debugger); + void _remote_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button); void _remote_object_updated(ObjectID p_id, int p_debugger); void _remote_object_property_updated(ObjectID p_id, const String &p_property, int p_debugger); void _remote_object_requested(ObjectID p_id, int p_debugger); diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp index 76efcd7190..4168efda26 100644 --- a/editor/debugger/editor_debugger_tree.cpp +++ b/editor/debugger/editor_debugger_tree.cpp @@ -65,6 +65,7 @@ void EditorDebuggerTree::_notification(int p_what) { void EditorDebuggerTree::_bind_methods() { ADD_SIGNAL(MethodInfo("object_selected", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::INT, "debugger"))); ADD_SIGNAL(MethodInfo("save_node", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "filename"), PropertyInfo(Variant::INT, "debugger"))); + ADD_SIGNAL(MethodInfo("open")); } void EditorDebuggerTree::_scene_tree_selected() { @@ -162,7 +163,7 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int } item->set_metadata(0, node.id); - // Set current item as collapsed if necessary (root is never collapsed) + // Set current item as collapsed if necessary (root is never collapsed). if (parent) { if (!unfold_cache.has(node.id)) { item->set_collapsed(true); @@ -178,7 +179,7 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int } } else { // Must use path if (last_path == _get_path(item)) { - updating_scene_tree = false; // Force emission of new selection + updating_scene_tree = false; // Force emission of new selection. item->select(0); if (filter_changed) { scroll_item = item; @@ -187,6 +188,33 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int } } + // Add buttons. + const Color remote_button_color = Color(1, 1, 1, 0.8); + if (!node.scene_file_path.is_empty()) { + String node_scene_file_path = node.scene_file_path; + Ref<Texture2D> button_icon = get_theme_icon(SNAME("InstanceOptions"), SNAME("EditorIcons")); + String tooltip = vformat(TTR("This node has been instantiated from a PackedScene file:\n%s\nClick to open the original file in the Editor."), node_scene_file_path); + + item->set_meta("scene_file_path", node_scene_file_path); + item->add_button(0, button_icon, BUTTON_SUBSCENE, false, tooltip); + item->set_button_color(0, item->get_button_count(0) - 1, remote_button_color); + } + + if (node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_HAS_VISIBLE_METHOD) { + bool node_visible = node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_VISIBLE; + bool node_visible_in_tree = node.view_flags & SceneDebuggerTree::RemoteNode::VIEW_VISIBLE_IN_TREE; + Ref<Texture2D> button_icon = get_theme_icon(node_visible ? SNAME("GuiVisibilityVisible") : SNAME("GuiVisibilityHidden"), SNAME("EditorIcons")); + String tooltip = TTR("Toggle Visibility"); + + item->set_meta("visible", node_visible); + item->add_button(0, button_icon, BUTTON_VISIBILITY, false, tooltip); + if (ClassDB::is_parent_class(node.type_name, "CanvasItem") || ClassDB::is_parent_class(node.type_name, "Node3D")) { + item->set_button_color(0, item->get_button_count(0) - 1, node_visible_in_tree ? remote_button_color : Color(1, 1, 1, 0.6)); + } else { + item->set_button_color(0, item->get_button_count(0) - 1, remote_button_color); + } + } + // Add in front of the parents stack if children are expected. if (node.child_count) { parents.push_front(Pair<TreeItem *, int>(item, node.child_count)); diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h index 5af3a0d84a..6d590cdb7a 100644 --- a/editor/debugger/editor_debugger_tree.h +++ b/editor/debugger/editor_debugger_tree.h @@ -65,6 +65,11 @@ protected: void _notification(int p_what); public: + enum Button { + BUTTON_SUBSCENE = 0, + BUTTON_VISIBILITY = 1, + }; + virtual Variant get_drag_data(const Point2 &p_point) override; String get_selected_path(); diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp index 83deff1469..0ff2227fed 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -1036,7 +1036,7 @@ void EditorAudioBuses::_notification(int p_what) { case NOTIFICATION_DRAG_END: { if (drop_end) { - drop_end->queue_delete(); + drop_end->queue_free(); drop_end = nullptr; } } break; diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index 98f21bda7a..acdf41c078 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -527,7 +527,7 @@ void EditorAutoloadSettings::update_autoload() { } if (info.node) { - info.node->queue_delete(); + info.node->queue_free(); info.node = nullptr; } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 5192680787..fc5b0345d0 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -2304,7 +2304,6 @@ EditorHelpBit::EditorHelpBit() { rich_text = memnew(RichTextLabel); add_child(rich_text); rich_text->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); - rich_text->set_override_selected_font_color(false); rich_text->set_fit_content_height(true); set_custom_minimum_size(Size2(0, 50 * EDSCALE)); } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 8ee82888f3..3b56d9ee2c 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -4534,7 +4534,7 @@ void EditorNode::_dock_floating_close_request(Control *p_control) { dock_slot[window_slot]->move_child(p_control, MIN((int)window->get_meta("dock_index"), dock_slot[window_slot]->get_tab_count())); dock_slot[window_slot]->set_current_tab(window->get_meta("dock_index")); - window->queue_delete(); + window->queue_free(); _update_dock_containers(); diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index 7cc42f4d24..bec05a0cd6 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -293,7 +293,7 @@ void EditorPropertyArray::update_property() { continue; // Don't remove the property that the user is moving. } - child->queue_delete(); // Button still needed after pressed is called. + child->queue_free(); // Button still needed after pressed is called. property_vbox->remove_child(child); } } @@ -861,7 +861,7 @@ void EditorPropertyDictionary::update_property() { } else { // Queue children for deletion, deleting immediately might cause errors. for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { - property_vbox->get_child(i)->queue_delete(); + property_vbox->get_child(i)->queue_free(); } } @@ -1349,7 +1349,7 @@ void EditorPropertyLocalizableString::update_property() { } else { // Queue children for deletion, deleting immediately might cause errors. for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { - property_vbox->get_child(i)->queue_delete(); + property_vbox->get_child(i)->queue_free(); } } diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 5bdfd8d377..6421183e56 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -777,7 +777,7 @@ void EditorSettings::_load_godot2_text_editor_theme() { _initial_set("text_editor/theme/highlighting/safe_line_number_color", Color(0.67, 0.78, 0.67, 0.6)); _initial_set("text_editor/theme/highlighting/caret_color", Color(0.67, 0.67, 0.67)); _initial_set("text_editor/theme/highlighting/caret_background_color", Color(0, 0, 0)); - _initial_set("text_editor/theme/highlighting/text_selected_color", Color(0, 0, 0)); + _initial_set("text_editor/theme/highlighting/text_selected_color", Color(0, 0, 0, 0)); _initial_set("text_editor/theme/highlighting/selection_color", Color(0.41, 0.61, 0.91, 0.35)); _initial_set("text_editor/theme/highlighting/brace_mismatch_color", Color(1, 0.2, 0.2)); _initial_set("text_editor/theme/highlighting/current_line_color", Color(0.3, 0.5, 0.8, 0.15)); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 1d9e320be1..225d767163 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1801,7 +1801,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { const Color safe_line_number_color = dark_theme ? (dim_color * Color(1, 1.2, 1, 1.5)) : Color(0, 0.4, 0, 0.75); const Color caret_color = mono_color; const Color caret_background_color = mono_color.inverted(); - const Color text_selected_color = dark_color_3; + const Color text_selected_color = Color(0, 0, 0, 0); const Color brace_mismatch_color = dark_theme ? error_color : Color(1, 0.08, 0, 1); const Color current_line_color = alpha1; const Color line_length_guideline_color = dark_theme ? base_color : background_color; diff --git a/editor/editor_toaster.cpp b/editor/editor_toaster.cpp index 6b2cc99df9..504c8f0432 100644 --- a/editor/editor_toaster.cpp +++ b/editor/editor_toaster.cpp @@ -231,7 +231,7 @@ void EditorToaster::_auto_hide_or_free_toasts() { // Delete the control right away (removed as child) as it might cause issues otherwise when iterative over the vbox_container children. for (unsigned int i = 0; i < to_delete.size(); i++) { vbox_container->remove_child(to_delete[i]); - to_delete[i]->queue_delete(); + to_delete[i]->queue_free(); toasts.erase(to_delete[i]); } diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index a0f4ade182..fd110cea4f 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -464,7 +464,7 @@ void FindInFilesDialog::_notification(int p_what) { _search_text_line_edit->select_all(); // Extensions might have changed in the meantime, we clean them and instance them again. for (int i = 0; i < _filters_container->get_child_count(); i++) { - _filters_container->get_child(i)->queue_delete(); + _filters_container->get_child(i)->queue_free(); } Array exts = ProjectSettings::get_singleton()->get("editor/script/search_in_file_extensions"); for (int i = 0; i < exts.size(); ++i) { diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp index 756d61f712..8ede88a888 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/resource_importer_scene.cpp @@ -1373,7 +1373,7 @@ Node *ResourceImporterScene::_post_fix_node(Node *p_node, Node *p_root, HashMap< } for (int i = 0; i < post_importer_plugins.size(); i++) { - post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION, p_root, p_node, anim, node_settings); + post_importer_plugins.write[i]->internal_process(EditorScenePostImportPlugin::INTERNAL_IMPORT_CATEGORY_ANIMATION, p_root, p_node, anim, anim_settings); } } } diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 2d1f4088a0..51b65e8eb0 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -460,7 +460,7 @@ void EditorAssetLibraryItemDownload::_notification(int p_what) { void EditorAssetLibraryItemDownload::_close() { // Clean up downloaded file. DirAccess::remove_file_or_error(download->get_download_file()); - queue_delete(); + queue_free(); } bool EditorAssetLibraryItemDownload::can_install() const { @@ -832,7 +832,7 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons } } - image_queue[p_queue_id].request->queue_delete(); + image_queue[p_queue_id].request->queue_free(); image_queue.erase(p_queue_id); _update_image_queue(); @@ -868,7 +868,7 @@ void EditorAssetLibrary::_update_image_queue() { } while (to_delete.size()) { - image_queue[to_delete.front()->get()].request->queue_delete(); + image_queue[to_delete.front()->get()].request->queue_free(); image_queue.erase(to_delete.front()->get()); to_delete.pop_front(); } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index fe33a91a41..7004e4d3a2 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5543,7 +5543,7 @@ void CanvasItemEditorViewport::_remove_preview() { if (preview_node->get_parent()) { for (int i = preview_node->get_child_count() - 1; i >= 0; i--) { Node *node = preview_node->get_child(i); - node->queue_delete(); + node->queue_free(); preview_node->remove_child(node); } EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node); diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp index 58256ae549..33992314b0 100644 --- a/editor/plugins/font_config_plugin.cpp +++ b/editor/plugins/font_config_plugin.cpp @@ -260,7 +260,7 @@ void EditorPropertyFontMetaOverride::update_property() { } else { // Queue children for deletion, deleting immediately might cause errors. for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { - property_vbox->get_child(i)->queue_delete(); + property_vbox->get_child(i)->queue_free(); } button_add = nullptr; } @@ -457,7 +457,7 @@ void EditorPropertyOTVariation::update_property() { } else { // Queue children for deletion, deleting immediately might cause errors. for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { - property_vbox->get_child(i)->queue_delete(); + property_vbox->get_child(i)->queue_free(); } } @@ -662,7 +662,7 @@ void EditorPropertyOTFeatures::update_property() { } else { // Queue children for deletion, deleting immediately might cause errors. for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { - property_vbox->get_child(i)->queue_delete(); + property_vbox->get_child(i)->queue_free(); } button_add = nullptr; } diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 26af5e3f2d..47088a2bac 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -1937,6 +1937,7 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { #undef ADD_QUAD p_gizmo->add_lines(lines, material); + p_gizmo->add_collision_segments(lines); p_gizmo->add_handles(handles, get_material("handles")); } diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 3c9d93f13a..40008b678a 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -3862,7 +3862,7 @@ void Node3DEditorViewport::_remove_preview_node() { if (preview_node->get_parent()) { for (int i = preview_node->get_child_count() - 1; i >= 0; i--) { Node *node = preview_node->get_child(i); - node->queue_delete(); + node->queue_free(); preview_node->remove_child(node); } EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 83245366f9..f2e84aec13 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -1082,7 +1082,7 @@ void Skeleton3DEditor::select_bone(int p_idx) { Skeleton3DEditor::~Skeleton3DEditor() { singleton = nullptr; - handles_mesh_instance->queue_delete(); + handles_mesh_instance->queue_free(); Node3DEditor *ne = Node3DEditor::get_singleton(); diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index e2ed8e44c4..5d02e4e437 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -2481,7 +2481,7 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = color_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = color_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); color_items_list->remove_child(node); } @@ -2510,7 +2510,7 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = constant_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = constant_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); constant_items_list->remove_child(node); } @@ -2543,7 +2543,7 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = font_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = font_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); font_items_list->remove_child(node); } @@ -2581,7 +2581,7 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = font_size_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = font_size_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); font_size_items_list->remove_child(node); } @@ -2614,7 +2614,7 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = icon_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = icon_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); icon_items_list->remove_child(node); } @@ -2652,7 +2652,7 @@ void ThemeTypeEditor::_update_type_items() { { for (int i = stylebox_items_list->get_child_count() - 1; i >= 0; i--) { Node *node = stylebox_items_list->get_child(i); - node->queue_delete(); + node->queue_free(); stylebox_items_list->remove_child(node); } diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp index 8cc96201e7..fdd8a80ad3 100644 --- a/editor/plugins/theme_editor_preview.cpp +++ b/editor/plugins/theme_editor_preview.cpp @@ -459,7 +459,7 @@ void SceneThemeEditorPreview::_reload_scene() { for (int i = preview_content->get_child_count() - 1; i >= 0; i--) { Node *node = preview_content->get_child(i); - node->queue_delete(); + node->queue_free(); preview_content->remove_child(node); } diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 17b9035121..e760c84f7d 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -1141,7 +1141,7 @@ void TileDataDefaultEditor::setup_property_editor(Variant::Type p_type, String p // Update everything. if (property_editor) { - property_editor->queue_delete(); + property_editor->queue_free(); } // Update the dummy object. @@ -1200,7 +1200,7 @@ TileDataDefaultEditor::TileDataDefaultEditor() { } TileDataDefaultEditor::~TileDataDefaultEditor() { - toolbar->queue_delete(); + toolbar->queue_free(); memdelete(dummy_object); } @@ -1403,11 +1403,11 @@ void TileDataCollisionEditor::_polygons_changed() { dummy_object->remove_dummy_property(vformat("polygon_%d_one_way_margin", i)); } for (int i = polygon_editor->get_polygon_count(); property_editors.has(vformat("polygon_%d_one_way", i)); i++) { - property_editors[vformat("polygon_%d_one_way", i)]->queue_delete(); + property_editors[vformat("polygon_%d_one_way", i)]->queue_free(); property_editors.erase(vformat("polygon_%d_one_way", i)); } for (int i = polygon_editor->get_polygon_count(); property_editors.has(vformat("polygon_%d_one_way_margin", i)); i++) { - property_editors[vformat("polygon_%d_one_way_margin", i)]->queue_delete(); + property_editors[vformat("polygon_%d_one_way_margin", i)]->queue_free(); property_editors.erase(vformat("polygon_%d_one_way_margin", i)); } } @@ -2658,7 +2658,7 @@ TileDataTerrainsEditor::TileDataTerrainsEditor() { } TileDataTerrainsEditor::~TileDataTerrainsEditor() { - toolbar->queue_delete(); + toolbar->queue_free(); memdelete(dummy_object); } diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 1857606f00..874057b7b9 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -671,7 +671,7 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { } } for (int i = tile_set->get_occlusion_layers_count(); tile_data_editors.has(vformat("occlusion_layer_%d", i)); i++) { - tile_data_editors[vformat("occlusion_layer_%d", i)]->queue_delete(); + tile_data_editors[vformat("occlusion_layer_%d", i)]->queue_free(); tile_data_editors.erase(vformat("occlusion_layer_%d", i)); } @@ -710,7 +710,7 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { } } for (int i = tile_set->get_physics_layers_count(); tile_data_editors.has(vformat("physics_layer_%d", i)); i++) { - tile_data_editors[vformat("physics_layer_%d", i)]->queue_delete(); + tile_data_editors[vformat("physics_layer_%d", i)]->queue_free(); tile_data_editors.erase(vformat("physics_layer_%d", i)); } @@ -728,7 +728,7 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { } } for (int i = tile_set->get_navigation_layers_count(); tile_data_editors.has(vformat("navigation_layer_%d", i)); i++) { - tile_data_editors[vformat("navigation_layer_%d", i)]->queue_delete(); + tile_data_editors[vformat("navigation_layer_%d", i)]->queue_free(); tile_data_editors.erase(vformat("navigation_layer_%d", i)); } @@ -750,7 +750,7 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { } } for (int i = tile_set->get_custom_data_layers_count(); tile_data_editors.has(vformat("custom_data_%d", i)); i++) { - tile_data_editors[vformat("custom_data_%d", i)]->queue_delete(); + tile_data_editors[vformat("custom_data_%d", i)]->queue_free(); tile_data_editors.erase(vformat("custom_data_%d", i)); } @@ -884,7 +884,7 @@ void TileSetAtlasSourceEditor::_update_atlas_view() { // Create a bunch of buttons to add alternative tiles. for (int i = 0; i < alternative_tiles_control->get_child_count(); i++) { - alternative_tiles_control->get_child(i)->queue_delete(); + alternative_tiles_control->get_child(i)->queue_free(); } Vector2i pos; diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index 3b057b7631..5e25d343b0 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -808,9 +808,3 @@ TileSetEditor::TileSetEditor() { EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element)); EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback)); } - -TileSetEditor::~TileSetEditor() { - if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed)); - } -} diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index 290c53b109..76a471db74 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -109,7 +109,6 @@ public: void edit(Ref<TileSet> p_tile_set); TileSetEditor(); - ~TileSetEditor(); }; #endif // TILE_SET_EDITOR_H diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 6fdc9a80e8..3937187e38 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -131,7 +131,7 @@ void TilesEditorPlugin::_thread() { Callable::CallError error; item.callback.callp(args_ptr, 2, r, error); - viewport->queue_delete(); + viewport->queue_free(); } } } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index ee358a8064..2787328efa 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1401,6 +1401,7 @@ void SceneTreeDock::_node_replace_owner(Node *p_base, Node *p_node, Node *p_root void SceneTreeDock::_load_request(const String &p_path) { EditorNode::get_singleton()->open_request(p_path); + _local_tree_selected(); } void SceneTreeDock::_script_open_request(const Ref<Script> &p_script) { @@ -3218,6 +3219,7 @@ void SceneTreeDock::add_remote_tree_editor(Control *p_remote) { add_child(p_remote); remote_tree = p_remote; remote_tree->hide(); + remote_tree->connect("open", callable_mp(this, &SceneTreeDock::_load_request)); } void SceneTreeDock::show_remote_tree() { @@ -3264,7 +3266,7 @@ void SceneTreeDock::_update_create_root_dialog() { EditorSettings::get_singleton()->save(); if (node_shortcuts_toggle->is_pressed()) { for (int i = 0; i < favorite_node_shortcuts->get_child_count(); i++) { - favorite_node_shortcuts->get_child(i)->queue_delete(); + favorite_node_shortcuts->get_child(i)->queue_free(); } Ref<FileAccess> f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorites.Node"), FileAccess::READ); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 898e4eb1a6..1401e4b94b 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1602,8 +1602,8 @@ void GDScriptAnalyzer::resolve_assert(GDScriptParser::AssertNode *p_assert) { reduce_expression(p_assert->condition); if (p_assert->message != nullptr) { reduce_expression(p_assert->message); - if (!p_assert->message->is_constant || p_assert->message->reduced_value.get_type() != Variant::STRING) { - push_error(R"(Expected constant string for assert error message.)", p_assert->message); + if (!p_assert->message->get_datatype().has_no_type() && (p_assert->message->get_datatype().kind != GDScriptParser::DataType::BUILTIN || p_assert->message->get_datatype().builtin_type != Variant::STRING)) { + push_error(R"(Expected string for assert error message.)", p_assert->message); } } @@ -2425,9 +2425,15 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a switch (err.error) { case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: { - PropertyInfo wrong_arg = function_info.arguments[err.argument]; + String expected_type_name; + if (err.argument < function_info.arguments.size()) { + expected_type_name = type_from_property(function_info.arguments[err.argument]).to_string(); + } else { + expected_type_name = Variant::get_type_name((Variant::Type)err.expected); + } + push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be %s but is %s.)*", function_name, err.argument + 1, - type_from_property(wrong_arg).to_string(), p_call->arguments[err.argument]->get_datatype().to_string()), + expected_type_name, p_call->arguments[err.argument]->get_datatype().to_string()), p_call->arguments[err.argument]); } break; case Callable::CallError::CALL_ERROR_INVALID_METHOD: diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp index d465467cf9..9bbe467352 100644 --- a/modules/jpg/image_loader_jpegd.cpp +++ b/modules/jpg/image_loader_jpegd.cpp @@ -33,8 +33,8 @@ #include "core/os/os.h" #include "core/string/print_string.h" -#include "thirdparty/jpeg-compressor/jpgd.h" -#include "thirdparty/jpeg-compressor/jpge.h" +#include <jpgd.h> +#include <jpge.h> #include <string.h> Error jpeg_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len) { @@ -132,10 +132,6 @@ static Ref<Image> _jpegd_mem_loader_func(const uint8_t *p_png, int p_size) { return img; } -static Error _jpgd_save_func(const String &p_path, const Ref<Image> &p_img, float p_quality) { - return OK; -} - class ImageLoaderJPGOSFile : public jpge::output_stream { public: Ref<FileAccess> f; @@ -157,21 +153,18 @@ public: } }; -static Vector<uint8_t> _jpgd_buffer_save_func(const Ref<Image> &p_img, float p_quality) { - ERR_FAIL_COND_V(p_img.is_null() || p_img->is_empty(), Vector<uint8_t>()); +static Error _jpgd_save_to_output_stream(jpge::output_stream *p_output_stream, const Ref<Image> &p_img, float p_quality) { + ERR_FAIL_COND_V(p_img.is_null() || p_img->is_empty(), ERR_INVALID_PARAMETER); Ref<Image> image = p_img; if (image->get_format() != Image::FORMAT_RGB8) { - image->convert(Image::FORMAT_ETC2_RGB8); + image->convert(Image::FORMAT_RGB8); } jpge::params p; p.m_quality = CLAMP(p_quality * 100, 1, 100); - Vector<uint8_t> output; - ImageLoaderJPGOSBuffer ob; - ob.buffer = &output; jpge::jpeg_encoder enc; - enc.init(&ob, image->get_width(), image->get_height(), 3, p); + enc.init(p_output_stream, image->get_width(), image->get_height(), 3, p); const uint8_t *src_data = image->get_data().ptr(); for (int i = 0; i < image->get_height(); i++) { @@ -180,9 +173,28 @@ static Vector<uint8_t> _jpgd_buffer_save_func(const Ref<Image> &p_img, float p_q enc.process_scanline(nullptr); + return OK; +} + +static Vector<uint8_t> _jpgd_buffer_save_func(const Ref<Image> &p_img, float p_quality) { + Vector<uint8_t> output; + ImageLoaderJPGOSBuffer ob; + ob.buffer = &output; + if (_jpgd_save_to_output_stream(&ob, p_img, p_quality) != OK) { + return Vector<uint8_t>(); + } return output; } +static Error _jpgd_save_func(const String &p_path, const Ref<Image> &p_img, float p_quality) { + Error err; + Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V_MSG(err, err, vformat("Can't save JPG at path: '%s'.", p_path)); + ImageLoaderJPGOSFile ob; + ob.f = file; + return _jpgd_save_to_output_stream(&ob, p_img, p_quality); +} + ImageLoaderJPG::ImageLoaderJPG() { Image::_jpg_mem_loader_func = _jpegd_mem_loader_func; Image::save_jpg_func = _jpgd_save_func; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs index d915eeac0b..d5d80df643 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -188,14 +188,14 @@ namespace Godot.SourceGenerators if (godotClassMethods.Length > 0) { source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, "); - source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n {\n"); + source.Append("NativeVariantPtrArgs args, out godot_variant ret)\n {\n"); foreach (var method in godotClassMethods) { GenerateMethodInvoker(method, source); } - source.Append(" return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n"); + source.Append(" return base.InvokeGodotClassMethod(method, args, out ret);\n"); source.Append(" }\n"); } @@ -364,7 +364,7 @@ namespace Godot.SourceGenerators source.Append(" if (method == MethodName."); source.Append(methodName); - source.Append(" && argCount == "); + source.Append(" && args.Count == "); source.Append(method.ParamTypes.Length); source.Append(") {\n"); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs index 4e443ce26e..50196b84f0 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -167,6 +167,7 @@ namespace Godot.SourceGenerators Common.ReportSignalDelegateSignatureMustReturnVoid(context, signalDelegateSymbol); } } + continue; } @@ -257,14 +258,14 @@ namespace Godot.SourceGenerators { source.Append( " protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, "); - source.Append("NativeVariantPtrArgs args, int argCount)\n {\n"); + source.Append("NativeVariantPtrArgs args)\n {\n"); foreach (var signal in godotSignalDelegates) { GenerateSignalEventInvoker(signal, source); } - source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n"); + source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args);\n"); source.Append(" }\n"); } @@ -404,7 +405,7 @@ namespace Godot.SourceGenerators source.Append(" if (signal == SignalName."); source.Append(signalName); - source.Append(" && argCount == "); + source.Append(" && args.Count == "); source.Append(invokeMethodData.ParamTypes.Length); source.Append(") {\n"); source.Append(" backing_"); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 3be8dd87c0..b90321b586 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -1614,7 +1614,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") << " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, " - << "NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n" + << "NativeVariantPtrArgs args, out godot_variant ret)\n" << INDENT1 "{\n"; for (const MethodInterface &imethod : itype.methods) { @@ -1630,7 +1630,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // We check both native names (snake_case) and proxy names (PascalCase) output << INDENT2 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name << " || method == MethodName." << imethod.proxy_name - << ") && argCount == " << itos(imethod.arguments.size()) + << ") && args.Count == " << itos(imethod.arguments.size()) << " && " << CS_METHOD_HAS_GODOT_CLASS_METHOD << "((godot_string_name)" << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name << ".NativeValue))\n" << INDENT2 "{\n"; @@ -1682,7 +1682,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } if (is_derived_type) { - output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, argCount, out ret);\n"; + output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, out ret);\n"; } else { output << INDENT2 "ret = default;\n" << INDENT2 "return false;\n"; @@ -2200,6 +2200,11 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output) { String arguments_sig; + String delegate_type_params; + + if (!p_isignal.arguments.is_empty()) { + delegate_type_params += "<"; + } // Retrieve information from the arguments const ArgumentInterface &first = p_isignal.arguments.front()->get(); @@ -2220,11 +2225,18 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf if (&iarg != &first) { arguments_sig += ", "; + delegate_type_params += ", "; } arguments_sig += arg_type->cs_type; arguments_sig += " "; arguments_sig += iarg.name; + + delegate_type_params += arg_type->cs_type; + } + + if (!p_isignal.arguments.is_empty()) { + delegate_type_params += ">"; } // Generate signal @@ -2248,15 +2260,46 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append("\")]"); } - String delegate_name = p_isignal.proxy_name; - delegate_name += "EventHandler"; // Delegate name is [SignalName]EventHandler + bool is_parameterless = p_isignal.arguments.size() == 0; - // Generate delegate - p_output.append(MEMBER_BEGIN "public delegate void "); - p_output.append(delegate_name); - p_output.append("("); - p_output.append(arguments_sig); - p_output.append(");\n"); + // Delegate name is [SignalName]EventHandler + String delegate_name = is_parameterless ? "Action" : p_isignal.proxy_name + "EventHandler"; + + if (!is_parameterless) { + // Generate delegate + p_output.append(MEMBER_BEGIN "public delegate void "); + p_output.append(delegate_name); + p_output.append("("); + p_output.append(arguments_sig); + p_output.append(");\n"); + + // Generate Callable trampoline for the delegate + p_output << MEMBER_BEGIN "private static unsafe void " << p_isignal.proxy_name << "Trampoline" + << "(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret)\n" + << INDENT1 "{\n" + << INDENT2 "Callable.ThrowIfArgCountMismatch(args, " << itos(p_isignal.arguments.size()) << ");\n" + << INDENT2 "((" << delegate_name << ")delegateObj)("; + + int idx = 0; + for (const ArgumentInterface &iarg : p_isignal.arguments) { + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + + if (idx != 0) { + p_output << ","; + } + + // TODO: We don't need to use VariantConversionCallbacks. We have the type information so we can use [cs_variant_to_managed] and [cs_managed_to_variant]. + p_output << "\n" INDENT3 "VariantConversionCallbacks.GetToManagedCallback<" + << arg_type->cs_type << ">()(args[" << itos(idx) << "])"; + + idx++; + } + + p_output << ");\n" + << INDENT2 "ret = default;\n" + << INDENT1 "}\n"; + } if (p_isignal.method_doc && p_isignal.method_doc->description.size()) { String xml_summary = bbcode_to_xml(fix_doc_description(p_isignal.method_doc->description), &p_itype); @@ -2292,6 +2335,11 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append("static "); } + if (!is_parameterless) { + // `unsafe` is needed for taking the trampoline's function pointer + p_output << "unsafe "; + } + p_output.append("event "); p_output.append(delegate_name); p_output.append(" "); @@ -2304,8 +2352,13 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append("add => Connect(SignalName."); } - p_output.append(p_isignal.proxy_name); - p_output.append(", new Callable(value));\n"); + if (is_parameterless) { + // Delegate type is Action. No need for custom trampoline. + p_output << p_isignal.proxy_name << ", Callable.From(value));\n"; + } else { + p_output << p_isignal.proxy_name + << ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n"; + } if (p_itype.is_singleton) { p_output.append(INDENT2 "remove => " CS_PROPERTY_SINGLETON ".Disconnect(SignalName."); @@ -2313,8 +2366,14 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append(INDENT2 "remove => Disconnect(SignalName."); } - p_output.append(p_isignal.proxy_name); - p_output.append(", new Callable(value));\n"); + if (is_parameterless) { + // Delegate type is Action. No need for custom trampoline. + p_output << p_isignal.proxy_name << ", Callable.From(value));\n"; + } else { + p_output << p_isignal.proxy_name + << ", Callable.CreateWithUnsafeTrampoline(value, &" << p_isignal.proxy_name << "Trampoline));\n"; + } + p_output.append(CLOSE_BLOCK_L1); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index 1c98dfcdf6..f1b46e293b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -489,25 +489,37 @@ namespace Godot.Collections ICollection<T>, IEnumerable<T> { + private static godot_variant ToVariantFunc(in Array<T> godotArray) => + VariantUtils.CreateFromArray(godotArray); + + private static Array<T> FromVariantFunc(in godot_variant variant) => + VariantUtils.ConvertToArrayObject<T>(variant); + // ReSharper disable StaticMemberInGenericType // Warning is about unique static fields being created for each generic type combination: // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html // In our case this is exactly what we want. - private static unsafe delegate* managed<in T, godot_variant> _convertToVariantCallback; - private static unsafe delegate* managed<in godot_variant, T> _convertToManagedCallback; + private static readonly unsafe delegate* managed<in T, godot_variant> ConvertToVariantCallback; + private static readonly unsafe delegate* managed<in godot_variant, T> ConvertToManagedCallback; // ReSharper restore StaticMemberInGenericType static unsafe Array() { - _convertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>(); - _convertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>(); + VariantConversionCallbacks.GenericConversionCallbacks[typeof(Array<T>)] = + ( + (IntPtr)(delegate* managed<in Array<T>, godot_variant>)&ToVariantFunc, + (IntPtr)(delegate* managed<in godot_variant, Array<T>>)&FromVariantFunc + ); + + ConvertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>(); + ConvertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>(); } private static unsafe void ValidateVariantConversionCallbacks() { - if (_convertToVariantCallback == null || _convertToManagedCallback == null) + if (ConvertToVariantCallback == null || ConvertToManagedCallback == null) { throw new InvalidOperationException( $"The array element type is not supported for conversion to Variant: '{typeof(T).FullName}'."); @@ -653,7 +665,7 @@ namespace Godot.Collections get { _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem); - return _convertToManagedCallback(borrowElem); + return ConvertToManagedCallback(borrowElem); } set { @@ -663,7 +675,7 @@ namespace Godot.Collections godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); godot_variant* itemPtr = &ptrw[index]; (*itemPtr).Dispose(); - *itemPtr = _convertToVariantCallback(value); + *itemPtr = ConvertToVariantCallback(value); } } @@ -675,7 +687,7 @@ namespace Godot.Collections /// <returns>The index of the item, or -1 if not found.</returns> public unsafe int IndexOf(T item) { - using var variantValue = _convertToVariantCallback(item); + using var variantValue = ConvertToVariantCallback(item); var self = (godot_array)_underlyingArray.NativeValue; return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); } @@ -693,7 +705,7 @@ namespace Godot.Collections if (index < 0 || index > Count) throw new ArgumentOutOfRangeException(nameof(index)); - using var variantValue = _convertToVariantCallback(item); + using var variantValue = ConvertToVariantCallback(item); var self = (godot_array)_underlyingArray.NativeValue; NativeFuncs.godotsharp_array_insert(ref self, index, variantValue); } @@ -726,7 +738,7 @@ namespace Godot.Collections /// <returns>The new size after adding the item.</returns> public unsafe void Add(T item) { - using var variantValue = _convertToVariantCallback(item); + using var variantValue = ConvertToVariantCallback(item); var self = (godot_array)_underlyingArray.NativeValue; _ = NativeFuncs.godotsharp_array_add(ref self, variantValue); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs index ae44f8f4ba..354212da1b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs @@ -22,8 +22,7 @@ namespace Godot.Bridge } bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method), - new NativeVariantPtrArgs(args), - argCount, out godot_variant retValue); + new NativeVariantPtrArgs(args, argCount), out godot_variant retValue); if (!methodInvoked) { @@ -102,7 +101,7 @@ namespace Godot.Bridge return godot_bool.False; } - *outRet = Marshaling.ConvertManagedObjectToVariant(ret); + *outRet = ret.CopyNativeVariant(); return godot_bool.True; } catch (Exception e) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs index 57240624bc..44ea8fc83d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs @@ -9,7 +9,7 @@ namespace Godot.Bridge { // @formatter:off public delegate* unmanaged<IntPtr, godot_variant**, int, godot_bool*, void> SignalAwaiter_SignalCallback; - public delegate* unmanaged<IntPtr, godot_variant**, uint, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs; + public delegate* unmanaged<IntPtr, void*, godot_variant**, int, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs; public delegate* unmanaged<IntPtr, IntPtr, godot_bool> DelegateUtils_DelegateEquals; public delegate* unmanaged<IntPtr, godot_array*, godot_bool> DelegateUtils_TrySerializeDelegateWithGCHandle; public delegate* unmanaged<godot_array*, IntPtr*, godot_bool> DelegateUtils_TryDeserializeDelegateWithGCHandle; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs index 092724a6b1..d83cf43eb2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -339,7 +339,7 @@ namespace Godot.Bridge *outOwnerIsNull = godot_bool.False; owner.RaiseGodotClassSignalCallbacks(CustomUnsafe.AsRef(eventSignalName), - new NativeVariantPtrArgs(args), argCount); + new NativeVariantPtrArgs(args, argCount)); } catch (Exception e) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index bdedd2e87a..f9309ca13e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -26,11 +26,12 @@ namespace Godot /// } /// </code> /// </example> - public readonly struct Callable + public readonly partial struct Callable { private readonly Object _target; private readonly StringName _method; private readonly Delegate _delegate; + private readonly unsafe delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> _trampoline; /// <summary> /// Object that contains the method. @@ -48,10 +49,10 @@ namespace Godot public Delegate Delegate => _delegate; /// <summary> - /// Converts a <see cref="Delegate"/> to a <see cref="Callable"/>. + /// Trampoline function pointer for dynamically invoking <see cref="Callable.Delegate"/>. /// </summary> - /// <param name="delegate">The delegate to convert.</param> - public static implicit operator Callable(Delegate @delegate) => new Callable(@delegate); + public unsafe delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> Trampoline + => _trampoline; /// <summary> /// Constructs a new <see cref="Callable"/> for the method called <paramref name="method"/> @@ -59,22 +60,21 @@ namespace Godot /// </summary> /// <param name="target">Object that contains the method.</param> /// <param name="method">Name of the method that will be called.</param> - public Callable(Object target, StringName method) + public unsafe Callable(Object target, StringName method) { _target = target; _method = method; _delegate = null; + _trampoline = null; } - /// <summary> - /// Constructs a new <see cref="Callable"/> for the given <paramref name="delegate"/>. - /// </summary> - /// <param name="delegate">Delegate method that will be called.</param> - public Callable(Delegate @delegate) + private unsafe Callable(Delegate @delegate, + delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> trampoline) { _target = @delegate?.Target as Object; _method = null; _delegate = @delegate; + _trampoline = trampoline; } private const int VarArgsSpanThreshold = 5; @@ -149,5 +149,59 @@ namespace Godot NativeFuncs.godotsharp_callable_call_deferred(callable, (godot_variant**)argsPtr, argc); } } + + /// <summary> + /// <para> + /// Constructs a new <see cref="Callable"/> using the <paramref name="trampoline"/> + /// function pointer to dynamically invoke the given <paramref name="delegate"/>. + /// </para> + /// <para> + /// The parameters passed to the <paramref name="trampoline"/> function are: + /// </para> + /// <list type="number"> + /// <item> + /// <term>delegateObj</term> + /// <description>The given <paramref name="delegate"/>, upcast to <see cref="object"/>.</description> + /// </item> + /// <item> + /// <term>args</term> + /// <description>Array of <see cref="godot_variant"/> arguments.</description> + /// </item> + /// <item> + /// <term>ret</term> + /// <description>Return value of type <see cref="godot_variant"/>.</description> + /// </item> + ///</list> + /// <para> + /// The delegate should be downcast to a more specific delegate type before invoking. + /// </para> + /// </summary> + /// <example> + /// Usage example: + /// + /// <code> + /// static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + /// { + /// if (args.Count != 1) + /// throw new ArgumentException($"Callable expected {1} arguments but received {args.Count}."); + /// + /// TResult res = ((Func<int, string>)delegateObj)( + /// VariantConversionCallbacks.GetToManagedCallback<int>()(args[0]) + /// ); + /// + /// ret = VariantConversionCallbacks.GetToVariantCallback<string>()(res); + /// } + /// + /// var callable = Callable.CreateWithUnsafeTrampoline((int num) => "foo" + num.ToString(), &Trampoline); + /// var res = (string)callable.Call(10); + /// Console.WriteLine(res); + /// </code> + /// </example> + /// <param name="delegate">Delegate method that will be called.</param> + /// <param name="trampoline">Trampoline function pointer for invoking the delegate.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe Callable CreateWithUnsafeTrampoline(Delegate @delegate, + delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void> trampoline) + => new(@delegate, trampoline); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs new file mode 100644 index 0000000000..6c6a104019 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.generics.cs @@ -0,0 +1,480 @@ +using System; +using System.Runtime.CompilerServices; +using Godot.NativeInterop; + +namespace Godot; + +#nullable enable + +public readonly partial struct Callable +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ThrowIfArgCountMismatch(NativeVariantPtrArgs args, int countExpected, + [CallerArgumentExpression("args")] string? paramName = null) + { + if (countExpected != args.Count) + ThrowArgCountMismatch(countExpected, args.Count, paramName); + + static void ThrowArgCountMismatch(int countExpected, int countReceived, string? paramName) + { + throw new ArgumentException( + "Invalid argument count for invoking callable." + + $" Expected {countExpected} arguments, received {countReceived}.", + paramName); + } + } + + /// <summary> + /// Constructs a new <see cref="Callable"/> for the given <paramref name="action"/>. + /// </summary> + /// <param name="action">Action method that will be called.</param> + public static unsafe Callable From( + Action action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 0); + + ((Action)delegateObj)(); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// <inheritdoc cref="From(Action)"/> + public static unsafe Callable From<T0>( + Action<T0> action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 1); + + ((Action<T0>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// <inheritdoc cref="From(Action)"/> + public static unsafe Callable From<T0, T1>( + Action<T0, T1> action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 2); + + ((Action<T0, T1>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// <inheritdoc cref="From(Action)"/> + public static unsafe Callable From<T0, T1, T2>( + Action<T0, T1, T2> action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 3); + + ((Action<T0, T1, T2>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// <inheritdoc cref="From(Action)"/> + public static unsafe Callable From<T0, T1, T2, T3>( + Action<T0, T1, T2, T3> action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 4); + + ((Action<T0, T1, T2, T3>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// <inheritdoc cref="From(Action)"/> + public static unsafe Callable From<T0, T1, T2, T3, T4>( + Action<T0, T1, T2, T3, T4> action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 5); + + ((Action<T0, T1, T2, T3, T4>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), + VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// <inheritdoc cref="From(Action)"/> + public static unsafe Callable From<T0, T1, T2, T3, T4, T5>( + Action<T0, T1, T2, T3, T4, T5> action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 6); + + ((Action<T0, T1, T2, T3, T4, T5>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), + VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), + VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// <inheritdoc cref="From(Action)"/> + public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6>( + Action<T0, T1, T2, T3, T4, T5, T6> action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 7); + + ((Action<T0, T1, T2, T3, T4, T5, T6>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), + VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), + VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), + VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// <inheritdoc cref="From(Action)"/> + public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7>( + Action<T0, T1, T2, T3, T4, T5, T6, T7> action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 8); + + ((Action<T0, T1, T2, T3, T4, T5, T6, T7>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), + VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), + VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), + VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), + VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// <inheritdoc cref="From(Action)"/> + public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7, T8>( + Action<T0, T1, T2, T3, T4, T5, T6, T7, T8> action + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 9); + + ((Action<T0, T1, T2, T3, T4, T5, T6, T7, T8>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), + VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), + VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), + VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), + VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]), + VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8]) + ); + + ret = default; + } + + return CreateWithUnsafeTrampoline(action, &Trampoline); + } + + /// <summary> + /// Constructs a new <see cref="Callable"/> for the given <paramref name="func"/>. + /// </summary> + /// <param name="func">Action method that will be called.</param> + public static unsafe Callable From<TResult>( + Func<TResult> func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 0); + + TResult res = ((Func<TResult>)delegateObj)(); + + ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// <inheritdoc cref="From{TResult}(Func{TResult})"/> + public static unsafe Callable From<T0, TResult>( + Func<T0, TResult> func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 1); + + TResult res = ((Func<T0, TResult>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// <inheritdoc cref="From{TResult}(Func{TResult})"/> + public static unsafe Callable From<T0, T1, TResult>( + Func<T0, T1, TResult> func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 2); + + TResult res = ((Func<T0, T1, TResult>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// <inheritdoc cref="From{TResult}(Func{TResult})"/> + public static unsafe Callable From<T0, T1, T2, TResult>( + Func<T0, T1, T2, TResult> func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 3); + + TResult res = ((Func<T0, T1, T2, TResult>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// <inheritdoc cref="From{TResult}(Func{TResult})"/> + public static unsafe Callable From<T0, T1, T2, T3, TResult>( + Func<T0, T1, T2, T3, TResult> func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 4); + + TResult res = ((Func<T0, T1, T2, T3, TResult>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// <inheritdoc cref="From{TResult}(Func{TResult})"/> + public static unsafe Callable From<T0, T1, T2, T3, T4, TResult>( + Func<T0, T1, T2, T3, T4, TResult> func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 5); + + TResult res = ((Func<T0, T1, T2, T3, T4, TResult>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), + VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// <inheritdoc cref="From{TResult}(Func{TResult})"/> + public static unsafe Callable From<T0, T1, T2, T3, T4, T5, TResult>( + Func<T0, T1, T2, T3, T4, T5, TResult> func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 6); + + TResult res = ((Func<T0, T1, T2, T3, T4, T5, TResult>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), + VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), + VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// <inheritdoc cref="From{TResult}(Func{TResult})"/> + public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, TResult>( + Func<T0, T1, T2, T3, T4, T5, T6, TResult> func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 7); + + TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, TResult>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), + VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), + VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), + VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// <inheritdoc cref="From{TResult}(Func{TResult})"/> + public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7, TResult>( + Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult> func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 8); + + TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, TResult>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), + VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), + VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), + VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), + VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } + + /// <inheritdoc cref="From{TResult}(Func{TResult})"/> + public static unsafe Callable From<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult>( + Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult> func + ) + { + static void Trampoline(object delegateObj, NativeVariantPtrArgs args, out godot_variant ret) + { + ThrowIfArgCountMismatch(args, 9); + + TResult res = ((Func<T0, T1, T2, T3, T4, T5, T6, T7, T8, TResult>)delegateObj)( + VariantConversionCallbacks.GetToManagedCallback<T0>()(args[0]), + VariantConversionCallbacks.GetToManagedCallback<T1>()(args[1]), + VariantConversionCallbacks.GetToManagedCallback<T2>()(args[2]), + VariantConversionCallbacks.GetToManagedCallback<T3>()(args[3]), + VariantConversionCallbacks.GetToManagedCallback<T4>()(args[4]), + VariantConversionCallbacks.GetToManagedCallback<T5>()(args[5]), + VariantConversionCallbacks.GetToManagedCallback<T6>()(args[6]), + VariantConversionCallbacks.GetToManagedCallback<T7>()(args[7]), + VariantConversionCallbacks.GetToManagedCallback<T8>()(args[8]) + ); + + ret = VariantConversionCallbacks.GetToVariantCallback<TResult>()(res); + } + + return CreateWithUnsafeTrampoline(func, &Trampoline); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs index 9b3969d453..d19e0c08f2 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -30,33 +30,23 @@ namespace Godot } [UnmanagedCallersOnly] - internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, godot_variant** args, uint argc, - godot_variant* outRet) + internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, void* trampoline, + godot_variant** args, int argc, godot_variant* outRet) { try { - // TODO: Optimize - var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!; - var managedArgs = new object?[argc]; - - var parameterInfos = @delegate.Method.GetParameters(); - var paramsLength = parameterInfos.Length; - - if (argc != paramsLength) + if (trampoline == null) { - throw new InvalidOperationException( - $"The delegate expects {paramsLength} arguments, but received {argc}."); + throw new ArgumentNullException(nameof(trampoline), + "Cannot dynamically invoke delegate because the trampoline is null."); } - for (uint i = 0; i < argc; i++) - { - managedArgs[i] = Marshaling.ConvertVariantToManagedObjectOfType( - *args[i], parameterInfos[i].ParameterType); - } + var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!; + var trampolineFn = (delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void>)trampoline; - object? invokeRet = @delegate.DynamicInvoke(managedArgs); + trampolineFn(@delegate, new NativeVariantPtrArgs(args, argc), out godot_variant ret); - *outRet = Marshaling.ConvertManagedObjectToVariant(invokeRet); + *outRet = ret; } catch (Exception e) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index 93103d0f6b..f8793332a0 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -356,35 +356,47 @@ namespace Godot.Collections IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> { + private static godot_variant ToVariantFunc(in Dictionary<TKey, TValue> godotDictionary) => + VariantUtils.CreateFromDictionary(godotDictionary); + + private static Dictionary<TKey, TValue> FromVariantFunc(in godot_variant variant) => + VariantUtils.ConvertToDictionaryObject<TKey, TValue>(variant); + // ReSharper disable StaticMemberInGenericType // Warning is about unique static fields being created for each generic type combination: // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html // In our case this is exactly what we want. - private static unsafe delegate* managed<in TKey, godot_variant> _convertKeyToVariantCallback; - private static unsafe delegate* managed<in godot_variant, TKey> _convertKeyToManagedCallback; - private static unsafe delegate* managed<in TValue, godot_variant> _convertValueToVariantCallback; - private static unsafe delegate* managed<in godot_variant, TValue> _convertValueToManagedCallback; + private static readonly unsafe delegate* managed<in TKey, godot_variant> ConvertKeyToVariantCallback; + private static readonly unsafe delegate* managed<in godot_variant, TKey> ConvertKeyToManagedCallback; + private static readonly unsafe delegate* managed<in TValue, godot_variant> ConvertValueToVariantCallback; + private static readonly unsafe delegate* managed<in godot_variant, TValue> ConvertValueToManagedCallback; // ReSharper restore StaticMemberInGenericType static unsafe Dictionary() { - _convertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>(); - _convertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>(); - _convertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>(); - _convertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>(); + VariantConversionCallbacks.GenericConversionCallbacks[typeof(Dictionary<TKey, TValue>)] = + ( + (IntPtr)(delegate* managed<in Dictionary<TKey, TValue>, godot_variant>)&ToVariantFunc, + (IntPtr)(delegate* managed<in godot_variant, Dictionary<TKey, TValue>>)&FromVariantFunc + ); + + ConvertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>(); + ConvertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>(); + ConvertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>(); + ConvertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>(); } private static unsafe void ValidateVariantConversionCallbacks() { - if (_convertKeyToVariantCallback == null || _convertKeyToManagedCallback == null) + if (ConvertKeyToVariantCallback == null || ConvertKeyToManagedCallback == null) { throw new InvalidOperationException( $"The dictionary key type is not supported for conversion to Variant: '{typeof(TKey).FullName}'."); } - if (_convertValueToVariantCallback == null || _convertValueToManagedCallback == null) + if (ConvertValueToVariantCallback == null || ConvertValueToManagedCallback == null) { throw new InvalidOperationException( $"The dictionary value type is not supported for conversion to Variant: '{typeof(TValue).FullName}'."); @@ -473,14 +485,14 @@ namespace Godot.Collections { get { - using var variantKey = _convertKeyToVariantCallback(key); + using var variantKey = ConvertKeyToVariantCallback(key); var self = (godot_dictionary)_underlyingDict.NativeValue; if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant value).ToBool()) { using (value) - return _convertValueToManagedCallback(value); + return ConvertValueToManagedCallback(value); } else { @@ -489,8 +501,8 @@ namespace Godot.Collections } set { - using var variantKey = _convertKeyToVariantCallback(key); - using var variantValue = _convertValueToVariantCallback(value); + using var variantKey = ConvertKeyToVariantCallback(key); + using var variantValue = ConvertValueToVariantCallback(value); var self = (godot_dictionary)_underlyingDict.NativeValue; NativeFuncs.godotsharp_dictionary_set_value(ref self, variantKey, variantValue); @@ -539,8 +551,8 @@ namespace Godot.Collections using (value) { return new KeyValuePair<TKey, TValue>( - _convertKeyToManagedCallback(key), - _convertValueToManagedCallback(value)); + ConvertKeyToManagedCallback(key), + ConvertValueToManagedCallback(value)); } } @@ -552,13 +564,13 @@ namespace Godot.Collections /// <param name="value">The object to add.</param> public unsafe void Add(TKey key, TValue value) { - using var variantKey = _convertKeyToVariantCallback(key); + using var variantKey = ConvertKeyToVariantCallback(key); var self = (godot_dictionary)_underlyingDict.NativeValue; if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool()) throw new ArgumentException("An element with the same key already exists.", nameof(key)); - using var variantValue = _convertValueToVariantCallback(value); + using var variantValue = ConvertValueToVariantCallback(value); NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue); } @@ -569,7 +581,7 @@ namespace Godot.Collections /// <returns>Whether or not this dictionary contains the given key.</returns> public unsafe bool ContainsKey(TKey key) { - using var variantKey = _convertKeyToVariantCallback(key); + using var variantKey = ConvertKeyToVariantCallback(key); var self = (godot_dictionary)_underlyingDict.NativeValue; return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool(); } @@ -580,7 +592,7 @@ namespace Godot.Collections /// <param name="key">The key of the element to remove.</param> public unsafe bool Remove(TKey key) { - using var variantKey = _convertKeyToVariantCallback(key); + using var variantKey = ConvertKeyToVariantCallback(key); var self = (godot_dictionary)_underlyingDict.NativeValue; return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool(); } @@ -593,13 +605,13 @@ namespace Godot.Collections /// <returns>If an object was found for the given <paramref name="key"/>.</returns> public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { - using var variantKey = _convertKeyToVariantCallback(key); + using var variantKey = ConvertKeyToVariantCallback(key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); using (retValue) - value = found ? _convertValueToManagedCallback(retValue) : default; + value = found ? ConvertValueToManagedCallback(retValue) : default; return found; } @@ -625,7 +637,7 @@ namespace Godot.Collections unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { - using var variantKey = _convertKeyToVariantCallback(item.Key); + using var variantKey = ConvertKeyToVariantCallback(item.Key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); @@ -635,7 +647,7 @@ namespace Godot.Collections if (!found) return false; - using var variantValue = _convertValueToVariantCallback(item.Value); + using var variantValue = ConvertValueToVariantCallback(item.Value); return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool(); } } @@ -670,7 +682,7 @@ namespace Godot.Collections unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { - using var variantKey = _convertKeyToVariantCallback(item.Key); + using var variantKey = ConvertKeyToVariantCallback(item.Key); var self = (godot_dictionary)_underlyingDict.NativeValue; bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, variantKey, out godot_variant retValue).ToBool(); @@ -680,7 +692,7 @@ namespace Godot.Collections if (!found) return false; - using var variantValue = _convertValueToVariantCallback(item.Value); + using var variantValue = ConvertValueToVariantCallback(item.Value); if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool()) { return NativeFuncs.godotsharp_dictionary_remove_key( @@ -717,6 +729,7 @@ namespace Godot.Collections public static implicit operator Variant(Dictionary<TKey, TValue> from) => Variant.CreateFrom(from); [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator Dictionary<TKey, TValue>(Variant from) => from.AsGodotDictionary<TKey, TValue>(); + public static explicit operator Dictionary<TKey, TValue>(Variant from) => + from.AsGodotDictionary<TKey, TValue>(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs index 76b186cd15..649661ee06 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -721,10 +721,19 @@ namespace Godot.NativeInterop if (p_managed_callable.Delegate != null) { var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate); - IntPtr objectPtr = p_managed_callable.Target != null ? Object.GetPtr(p_managed_callable.Target) : IntPtr.Zero; - NativeFuncs.godotsharp_callable_new_with_delegate( - GCHandle.ToIntPtr(gcHandle), objectPtr, out godot_callable callable); - return callable; + + IntPtr objectPtr = p_managed_callable.Target != null ? + Object.GetPtr(p_managed_callable.Target) : + IntPtr.Zero; + + unsafe + { + NativeFuncs.godotsharp_callable_new_with_delegate( + GCHandle.ToIntPtr(gcHandle), (IntPtr)p_managed_callable.Trampoline, + objectPtr, out godot_callable callable); + + return callable; + } } else { @@ -748,19 +757,22 @@ namespace Godot.NativeInterop public static Callable ConvertCallableToManaged(in godot_callable p_callable) { if (NativeFuncs.godotsharp_callable_get_data_for_marshalling(p_callable, - out IntPtr delegateGCHandle, out IntPtr godotObject, - out godot_string_name name).ToBool()) + out IntPtr delegateGCHandle, out IntPtr trampoline, + out IntPtr godotObject, out godot_string_name name).ToBool()) { if (delegateGCHandle != IntPtr.Zero) { - return new Callable((Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target); - } - else - { - return new Callable( - InteropUtils.UnmanagedGetManaged(godotObject), - StringName.CreateTakingOwnershipOfDisposableValue(name)); + unsafe + { + return Callable.CreateWithUnsafeTrampoline( + (Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target, + (delegate* managed<object, NativeVariantPtrArgs, out godot_variant, void>)trampoline); + } } + + return new Callable( + InteropUtils.UnmanagedGetManaged(godotObject), + StringName.CreateTakingOwnershipOfDisposableValue(name)); } // Some other unsupported callable diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 20ede9f0dd..088f4e7ecf 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -141,11 +141,11 @@ namespace Godot.NativeInterop public static partial void godotsharp_packed_string_array_add(ref godot_packed_string_array r_dest, in godot_string p_element); - public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, IntPtr p_object, - out godot_callable r_callable); + public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, IntPtr p_trampoline, + IntPtr p_object, out godot_callable r_callable); internal static partial godot_bool godotsharp_callable_get_data_for_marshalling(in godot_callable p_callable, - out IntPtr r_delegate_handle, out IntPtr r_object, out godot_string_name r_name); + out IntPtr r_delegate_handle, out IntPtr r_trampoline, out IntPtr r_object, out godot_string_name r_name); internal static partial godot_variant godotsharp_callable_call(in godot_callable p_callable, godot_variant** p_args, int p_arg_count, out godot_variant_call_error p_call_error); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs index 422df74c23..d8c5d99cb8 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs @@ -8,8 +8,22 @@ namespace Godot.NativeInterop public unsafe ref struct NativeVariantPtrArgs { private godot_variant** _args; + private int _argc; - internal NativeVariantPtrArgs(godot_variant** args) => _args = args; + internal NativeVariantPtrArgs(godot_variant** args, int argc) + { + _args = args; + _argc = argc; + } + + /// <summary> + /// Returns the number of arguments. + /// </summary> + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _argc; + } public ref godot_variant this[int index] { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs index 9cde62c7c5..4b3db0c01a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs @@ -1,10 +1,15 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace Godot.NativeInterop; +// TODO: Change VariantConversionCallbacks<T>. Store the callback in a static field for quick repeated access, instead of checking every time. internal static unsafe class VariantConversionCallbacks { + internal static System.Collections.Generic.Dictionary<Type, (IntPtr ToVariant, IntPtr FromVariant)> + GenericConversionCallbacks = new(); + [SuppressMessage("ReSharper", "RedundantNameQualifier")] internal static delegate*<in T, godot_variant> GetToVariantCallback<T>() { @@ -502,6 +507,26 @@ internal static unsafe class VariantConversionCallbacks &FromVariant; } + // TODO: + // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode. + // We could make the Godot collections implement an interface and use IsAssignableFrom instead. + // Or we could just skip the check and always look for a conversion callback for the type. + if (typeOfT.IsGenericType) + { + var genericTypeDef = typeOfT.GetGenericTypeDefinition(); + + if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) || + genericTypeDef == typeof(Godot.Collections.Array<>)) + { + RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle); + + if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion)) + { + return (delegate*<in T, godot_variant>)genericConversion.ToVariant; + } + } + } + return null; } @@ -1005,6 +1030,26 @@ internal static unsafe class VariantConversionCallbacks &ToVariant; } + // TODO: + // IsGenericType and GetGenericTypeDefinition don't work in NativeAOT's reflection-free mode. + // We could make the Godot collections implement an interface and use IsAssignableFrom instead. + // Or we could just skip the check and always look for a conversion callback for the type. + if (typeOfT.IsGenericType) + { + var genericTypeDef = typeOfT.GetGenericTypeDefinition(); + + if (genericTypeDef == typeof(Godot.Collections.Dictionary<,>) || + genericTypeDef == typeof(Godot.Collections.Array<>)) + { + RuntimeHelpers.RunClassConstructor(typeOfT.TypeHandle); + + if (GenericConversionCallbacks.TryGetValue(typeOfT, out var genericConversion)) + { + return (delegate*<in godot_variant, T>)genericConversion.FromVariant; + } + } + } + // ReSharper restore RedundantCast return null; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs index 5cb678c280..60ee6eb6f4 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs @@ -202,7 +202,7 @@ namespace Godot // ReSharper disable once VirtualMemberNeverOverridden.Global protected internal virtual void RaiseGodotClassSignalCallbacks(in godot_string_name signal, - NativeVariantPtrArgs args, int argCount) + NativeVariantPtrArgs args) { } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index a63b668387..e3fb254f49 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -52,6 +52,7 @@ <Compile Include="Core\AABB.cs" /> <Compile Include="Core\Bridge\GodotSerializationInfo.cs" /> <Compile Include="Core\Bridge\MethodInfo.cs" /> + <Compile Include="Core\Callable.generics.cs" /> <Compile Include="Core\CustomGCHandle.cs" /> <Compile Include="Core\Array.cs" /> <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" /> diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 2717b945f6..e20a88076a 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -447,15 +447,16 @@ void godotsharp_packed_string_array_add(PackedStringArray *r_dest, const String r_dest->append(*p_element); } -void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, const Object *p_object, Callable *r_callable) { +void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, void *p_trampoline, + const Object *p_object, Callable *r_callable) { // TODO: Use pooling for ManagedCallable instances. ObjectID objid = p_object ? p_object->get_instance_id() : ObjectID(); - CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle, objid)); + CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle, p_trampoline, objid)); memnew_placement(r_callable, Callable(managed_callable)); } bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable, - GCHandleIntPtr *r_delegate_handle, Object **r_object, StringName *r_name) { + GCHandleIntPtr *r_delegate_handle, void **r_trampoline, Object **r_object, StringName *r_name) { if (p_callable->is_custom()) { CallableCustom *custom = p_callable->get_custom(); CallableCustom::CompareEqualFunc compare_equal_func = custom->get_compare_equal_func(); @@ -463,18 +464,21 @@ bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable, if (compare_equal_func == ManagedCallable::compare_equal_func_ptr) { ManagedCallable *managed_callable = static_cast<ManagedCallable *>(custom); *r_delegate_handle = managed_callable->get_delegate(); + *r_trampoline = managed_callable->get_trampoline(); *r_object = nullptr; memnew_placement(r_name, StringName()); return true; } else if (compare_equal_func == SignalAwaiterCallable::compare_equal_func_ptr) { SignalAwaiterCallable *signal_awaiter_callable = static_cast<SignalAwaiterCallable *>(custom); *r_delegate_handle = { nullptr }; + *r_trampoline = nullptr; *r_object = ObjectDB::get_instance(signal_awaiter_callable->get_object()); memnew_placement(r_name, StringName(signal_awaiter_callable->get_signal())); return true; } else if (compare_equal_func == EventSignalCallable::compare_equal_func_ptr) { EventSignalCallable *event_signal_callable = static_cast<EventSignalCallable *>(custom); *r_delegate_handle = { nullptr }; + *r_trampoline = nullptr; *r_object = ObjectDB::get_instance(event_signal_callable->get_object()); memnew_placement(r_name, StringName(event_signal_callable->get_signal())); return true; @@ -482,11 +486,13 @@ bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable, // Some other CallableCustom. We only support ManagedCallable. *r_delegate_handle = { nullptr }; + *r_trampoline = nullptr; *r_object = nullptr; memnew_placement(r_name, StringName()); return false; } else { *r_delegate_handle = { nullptr }; + *r_trampoline = nullptr; *r_object = ObjectDB::get_instance(p_callable->get_object_id()); memnew_placement(r_name, StringName(p_callable->get_method())); return true; diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp index 0c2c533090..28edc41d98 100644 --- a/modules/mono/managed_callable.cpp +++ b/modules/mono/managed_callable.cpp @@ -92,7 +92,7 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant ERR_FAIL_COND(delegate_handle.value == nullptr); GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs( - delegate_handle, p_arguments, p_argcount, &r_return_value); + delegate_handle, trampoline, p_arguments, p_argcount, &r_return_value); r_call_error.error = Callable::CallError::CALL_OK; } @@ -106,7 +106,8 @@ void ManagedCallable::release_delegate_handle() { // Why you do this clang-format... /* clang-format off */ -ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id) : delegate_handle(p_delegate_handle), object_id(p_object_id) { +ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle, void *p_trampoline, ObjectID p_object_id) : + delegate_handle(p_delegate_handle), trampoline(p_trampoline), object_id(p_object_id) { #ifdef GD_MONO_HOT_RELOAD { MutexLock lock(instances_mutex); diff --git a/modules/mono/managed_callable.h b/modules/mono/managed_callable.h index 26cd164fb6..b3a137dedf 100644 --- a/modules/mono/managed_callable.h +++ b/modules/mono/managed_callable.h @@ -40,6 +40,7 @@ class ManagedCallable : public CallableCustom { friend class CSharpLanguage; GCHandleIntPtr delegate_handle; + void *trampoline = nullptr; ObjectID object_id; #ifdef GD_MONO_HOT_RELOAD @@ -58,6 +59,7 @@ public: void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; _FORCE_INLINE_ GCHandleIntPtr get_delegate() const { return delegate_handle; } + _FORCE_INLINE_ void *get_trampoline() const { return trampoline; } static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); @@ -67,7 +69,7 @@ public: void release_delegate_handle(); - ManagedCallable(GCHandleIntPtr p_delegate_handle, ObjectID p_object_id); + ManagedCallable(GCHandleIntPtr p_delegate_handle, void *p_trampoline, ObjectID p_object_id); ~ManagedCallable(); }; diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index 13b599fe55..9c26fa2b0a 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -74,7 +74,7 @@ struct ManagedCallbacks { using Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, void *p_def_vals, int32_t p_count); using FuncSignalAwaiter_SignalCallback = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, int32_t, bool *); - using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, uint32_t, const Variant *); + using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, void *, const Variant **, int32_t, const Variant *); using FuncDelegateUtils_DelegateEquals = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr); using FuncDelegateUtils_TrySerializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const Array *); using FuncDelegateUtils_TryDeserializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(const Array *, GCHandleIntPtr *); diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index da425076a4..659ce7316a 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -72,7 +72,7 @@ void SceneReplicationInterface::_free_remotes(const PeerInfo &p_info) { for (const KeyValue<uint32_t, ObjectID> &E : p_info.recv_nodes) { Node *node = tracked_nodes.has(E.value) ? get_id_as<Node>(E.value) : nullptr; ERR_CONTINUE(!node); - node->queue_delete(); + node->queue_free(); } } @@ -581,7 +581,7 @@ Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p if (node->get_parent() != nullptr) { node->get_parent()->remove_child(node); } - node->queue_delete(); + node->queue_free(); spawner->emit_signal(SNAME("despawned"), node); return OK; diff --git a/modules/openxr/editor/openxr_action_map_editor.cpp b/modules/openxr/editor/openxr_action_map_editor.cpp index 51c402d746..b03402e80d 100644 --- a/modules/openxr/editor/openxr_action_map_editor.cpp +++ b/modules/openxr/editor/openxr_action_map_editor.cpp @@ -128,7 +128,7 @@ void OpenXRActionMapEditor::_update_interaction_profiles() { interaction_profiles.remove_at(0); tabs->remove_child(interaction_profile); - interaction_profile->queue_delete(); + interaction_profile->queue_free(); } // in with the new... @@ -205,7 +205,7 @@ void OpenXRActionMapEditor::_on_remove_action_set(Object *p_action_set_editor) { action_map->remove_action_set(action_set); actionsets_vb->remove_child(action_set_editor); - action_set_editor->queue_delete(); + action_set_editor->queue_free(); } void OpenXRActionMapEditor::_on_action_removed() { @@ -290,7 +290,7 @@ void OpenXRActionMapEditor::_on_tab_button_pressed(int p_tab) { action_map->remove_interaction_profile(interaction_profile); tabs->remove_child(profile_editor); - profile_editor->queue_delete(); + profile_editor->queue_free(); } void OpenXRActionMapEditor::open_action_map(String p_path) { diff --git a/modules/openxr/editor/openxr_action_set_editor.cpp b/modules/openxr/editor/openxr_action_set_editor.cpp index 804808a6b9..3869146e8e 100644 --- a/modules/openxr/editor/openxr_action_set_editor.cpp +++ b/modules/openxr/editor/openxr_action_set_editor.cpp @@ -140,7 +140,7 @@ void OpenXRActionSetEditor::_on_remove_action(Object *p_action_editor) { // And remove it.... action_map->remove_action(action->get_name_with_set()); // remove it from the set and any interaction profile it relates to actions_vb->remove_child(action_editor); - action_editor->queue_delete(); + action_editor->queue_free(); // Let action map editor know so we can update our interaction profiles emit_signal("action_removed"); diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp index d46b4f39de..2b0ee50570 100644 --- a/platform/android/jni_utils.cpp +++ b/platform/android/jni_utils.cpp @@ -265,33 +265,33 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { if (name == "[D") { jdoubleArray arr = (jdoubleArray)obj; int fCount = env->GetArrayLength(arr); - PackedFloat32Array sarr; - sarr.resize(fCount); + PackedFloat64Array packed_array; + packed_array.resize(fCount); - real_t *w = sarr.ptrw(); + double *w = packed_array.ptrw(); for (int i = 0; i < fCount; i++) { double n; env->GetDoubleArrayRegion(arr, i, 1, &n); w[i] = n; } - return sarr; + return packed_array; } if (name == "[F") { jfloatArray arr = (jfloatArray)obj; int fCount = env->GetArrayLength(arr); - PackedFloat32Array sarr; - sarr.resize(fCount); + PackedFloat32Array packed_array; + packed_array.resize(fCount); - real_t *w = sarr.ptrw(); + float *w = packed_array.ptrw(); for (int i = 0; i < fCount; i++) { float n; env->GetFloatArrayRegion(arr, i, 1, &n); w[i] = n; } - return sarr; + return packed_array; } if (name == "[Ljava.lang.Object;") { diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 58172b6153..f7a66215b2 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -1863,7 +1863,7 @@ void TileMap::_scenes_update_dirty_quadrants(SelfList<TileMapQuadrant>::List &r_ for (const KeyValue<Vector2i, String> &E : q.scenes) { Node *node = get_node_or_null(E.value); if (node) { - node->queue_delete(); + node->queue_free(); } } @@ -1911,7 +1911,7 @@ void TileMap::_scenes_cleanup_quadrant(TileMapQuadrant *p_quadrant) { for (const KeyValue<Vector2i, String> &E : p_quadrant->scenes) { Node *node = get_node_or_null(E.value); if (node) { - node->queue_delete(); + node->queue_free(); } } @@ -4056,9 +4056,5 @@ TileMap::TileMap() { } TileMap::~TileMap() { - if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileMap::_tile_set_changed)); - } - _clear_internals(); } diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp index a45ef52452..45ff0a4b45 100644 --- a/scene/3d/ray_cast_3d.cpp +++ b/scene/3d/ray_cast_3d.cpp @@ -516,7 +516,7 @@ void RayCast3D::_clear_debug_shape() { MeshInstance3D *mi = static_cast<MeshInstance3D *>(debug_shape); if (mi->is_inside_tree()) { - mi->queue_delete(); + mi->queue_free(); } else { memdelete(mi); } diff --git a/scene/3d/shape_cast_3d.cpp b/scene/3d/shape_cast_3d.cpp index e7d1a8ec7d..03cbd984cd 100644 --- a/scene/3d/shape_cast_3d.cpp +++ b/scene/3d/shape_cast_3d.cpp @@ -619,7 +619,7 @@ void ShapeCast3D::_clear_debug_shape() { MeshInstance3D *mi = static_cast<MeshInstance3D *>(debug_shape); if (mi->is_inside_tree()) { - mi->queue_delete(); + mi->queue_free(); } else { memdelete(mi); } diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp index e306d00a51..59930a3fbb 100644 --- a/scene/animation/animation_player.cpp +++ b/scene/animation/animation_player.cpp @@ -2066,7 +2066,7 @@ Ref<AnimatedValuesBackup> AnimationPlayer::apply_reset(bool p_user_initiated) { // Forcing the use of the original root because the scene where original player belongs may be not the active one Ref<AnimatedValuesBackup> old_values = aux_player->backup_animated_values(get_node(get_root())); aux_player->seek(0.0f, true); - aux_player->queue_delete(); + aux_player->queue_free(); if (p_user_initiated) { Ref<AnimatedValuesBackup> new_values = aux_player->backup_animated_values(); diff --git a/scene/debugger/scene_debugger.cpp b/scene/debugger/scene_debugger.cpp index bfc3c25fe6..b4bdda9ecb 100644 --- a/scene/debugger/scene_debugger.cpp +++ b/scene/debugger/scene_debugger.cpp @@ -548,14 +548,36 @@ SceneDebuggerTree::SceneDebuggerTree(Node *p_root) { // Flatten tree into list, depth first, use stack to avoid recursion. List<Node *> stack; stack.push_back(p_root); + bool is_root = true; + const StringName &is_visible_sn = SNAME("is_visible"); + const StringName &is_visible_in_tree_sn = SNAME("is_visible_in_tree"); while (stack.size()) { Node *n = stack[0]; stack.pop_front(); + int count = n->get_child_count(); - nodes.push_back(RemoteNode(count, n->get_name(), n->get_class(), n->get_instance_id())); for (int i = 0; i < count; i++) { stack.push_front(n->get_child(count - i - 1)); } + + int view_flags = 0; + if (is_root) { + // Prevent root window visibility from being changed. + is_root = false; + } else if (n->has_method(is_visible_sn)) { + const Variant visible = n->call(is_visible_sn); + if (visible.get_type() == Variant::BOOL) { + view_flags = RemoteNode::VIEW_HAS_VISIBLE_METHOD; + view_flags |= uint8_t(visible) * RemoteNode::VIEW_VISIBLE; + } + if (n->has_method(is_visible_in_tree_sn)) { + const Variant visible_in_tree = n->call(is_visible_in_tree_sn); + if (visible_in_tree.get_type() == Variant::BOOL) { + view_flags |= uint8_t(visible_in_tree) * RemoteNode::VIEW_VISIBLE_IN_TREE; + } + } + } + nodes.push_back(RemoteNode(count, n->get_name(), n->get_class(), n->get_instance_id(), n->get_scene_file_path(), view_flags)); } } @@ -565,19 +587,23 @@ void SceneDebuggerTree::serialize(Array &p_arr) { p_arr.push_back(n.name); p_arr.push_back(n.type_name); p_arr.push_back(n.id); + p_arr.push_back(n.scene_file_path); + p_arr.push_back(n.view_flags); } } void SceneDebuggerTree::deserialize(const Array &p_arr) { int idx = 0; while (p_arr.size() > idx) { - ERR_FAIL_COND(p_arr.size() < 4); - CHECK_TYPE(p_arr[idx], INT); - CHECK_TYPE(p_arr[idx + 1], STRING); - CHECK_TYPE(p_arr[idx + 2], STRING); - CHECK_TYPE(p_arr[idx + 3], INT); - nodes.push_back(RemoteNode(p_arr[idx], p_arr[idx + 1], p_arr[idx + 2], p_arr[idx + 3])); - idx += 4; + ERR_FAIL_COND(p_arr.size() < 6); + CHECK_TYPE(p_arr[idx], INT); // child_count. + CHECK_TYPE(p_arr[idx + 1], STRING); // name. + CHECK_TYPE(p_arr[idx + 2], STRING); // type_name. + CHECK_TYPE(p_arr[idx + 3], INT); // id. + CHECK_TYPE(p_arr[idx + 4], STRING); // scene_file_path. + CHECK_TYPE(p_arr[idx + 5], INT); // view_flags. + nodes.push_back(RemoteNode(p_arr[idx], p_arr[idx + 1], p_arr[idx + 2], p_arr[idx + 3], p_arr[idx + 4], p_arr[idx + 5])); + idx += 6; } } diff --git a/scene/debugger/scene_debugger.h b/scene/debugger/scene_debugger.h index 911363f45d..fe35446aae 100644 --- a/scene/debugger/scene_debugger.h +++ b/scene/debugger/scene_debugger.h @@ -110,12 +110,23 @@ public: String name; String type_name; ObjectID id; + String scene_file_path; + uint8_t view_flags = 0; - RemoteNode(int p_child, const String &p_name, const String &p_type, ObjectID p_id) { + enum ViewFlags { + VIEW_HAS_VISIBLE_METHOD = 1 << 1, + VIEW_VISIBLE = 1 << 2, + VIEW_VISIBLE_IN_TREE = 1 << 3, + }; + + RemoteNode(int p_child, const String &p_name, const String &p_type, ObjectID p_id, const String p_scene_file_path, int p_view_flags) { child_count = p_child; name = p_name; type_name = p_type; id = p_id; + + scene_file_path = p_scene_file_path; + view_flags = p_view_flags; } RemoteNode() {} diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 8a5d04f49c..f61fa29a33 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -2859,7 +2859,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { offset = line_height; } - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } code_completion_options.push_back(option); } @@ -2970,7 +2972,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { if (string_to_complete.length() == 0) { code_completion_options.push_back(option); - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } continue; } @@ -3076,7 +3080,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { option.matches.append_array(ssq_matches); completion_options_subseq.push_back(option); } - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } } else if (!*ssq_lower) { // Matched the whole subsequence in s_lower. option.matches.clear(); @@ -3093,7 +3099,9 @@ void CodeEdit::_filter_code_completion_candidates_impl() { option.matches.append_array(ssq_lower_matches); completion_options_subseq_casei.push_back(option); } - max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + if (font.is_valid()) { + max_width = MAX(max_width, font->get_string_size(option.display, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width + offset); + } } } diff --git a/scene/gui/color_mode.cpp b/scene/gui/color_mode.cpp index 3a5013dabe..a063cd344a 100644 --- a/scene/gui/color_mode.cpp +++ b/scene/gui/color_mode.cpp @@ -284,46 +284,68 @@ Color ColorModeOKHSL::get_color() const { } void ColorModeOKHSL::slider_draw(int p_which) { - Vector<Vector2> pos; - pos.resize(4); - Vector<Color> col; - col.resize(4); HSlider *slider = color_picker->get_slider(p_which); Size2 size = slider->get_size(); - Color left_color; - Color right_color; - Color color = color_picker->get_pick_color(); const real_t margin = 16 * color_picker->get_theme_default_base_scale(); - if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); - - left_color = color; - left_color.a = 0; - right_color = color; - right_color.a = 1; - } else if (p_which == 0) { + if (p_which == 0) { // H Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); slider->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); slider->draw_texture_rect(hue, Rect2(Vector2(margin * -1, 0), Vector2(margin, size.x)), false); return; - } else { - Color s_col; - Color v_col; - s_col.set_ok_hsl(color.get_h(), 0, color.get_v()); - left_color = (p_which == 1) ? s_col : Color(0, 0, 0); - s_col.set_ok_hsl(color.get_h(), 1, color.get_v()); - v_col.set_ok_hsl(color.get_h(), color.get_s(), 1); - right_color = (p_which == 1) ? s_col : v_col; } - col.set(0, left_color); - col.set(1, right_color); - col.set(2, right_color); - col.set(3, left_color); - pos.set(0, Vector2(0, 0)); - pos.set(1, Vector2(size.x, 0)); - pos.set(2, Vector2(size.x, margin)); - pos.set(3, Vector2(0, margin)); + + Vector<Vector2> pos; + Vector<Color> col; + Color left_color; + Color right_color; + Color color = color_picker->get_pick_color(); + + if (p_which == 2) { // L + pos.resize(6); + col.resize(6); + left_color = Color(0, 0, 0); + Color middle_color; + middle_color.set_ok_hsl(color.get_ok_hsl_h(), color.get_ok_hsl_s(), 0.5); + right_color.set_ok_hsl(color.get_ok_hsl_h(), color.get_ok_hsl_s(), 1); + + col.set(0, left_color); + col.set(1, middle_color); + col.set(2, right_color); + col.set(3, right_color); + col.set(4, middle_color); + col.set(5, left_color); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x * 0.5, 0)); + pos.set(2, Vector2(size.x, 0)); + pos.set(3, Vector2(size.x, margin)); + pos.set(4, Vector2(size.x * 0.5, margin)); + pos.set(5, Vector2(0, margin)); + } else { // A / S + pos.resize(4); + col.resize(4); + + if (p_which == ColorPicker::SLIDER_COUNT) { + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); + + left_color = color; + left_color.a = 0; + right_color = color; + right_color.a = 1; + } else { + left_color.set_ok_hsl(color.get_ok_hsl_h(), 0, color.get_ok_hsl_l()); + right_color.set_ok_hsl(color.get_ok_hsl_h(), 1, color.get_ok_hsl_l()); + } + + col.set(0, left_color); + col.set(1, right_color); + col.set(2, right_color); + col.set(3, left_color); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); + } slider->draw_polygon(pos, col); } diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 929bf27be6..1009b36584 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -744,7 +744,7 @@ void ColorPicker::add_recent_preset(const Color &p_color) { if (recent_preset_hbc->get_child_count() >= PRESET_COLUMN_COUNT) { recent_preset_cache.pop_front(); recent_presets.pop_front(); - recent_preset_hbc->get_child(PRESET_COLUMN_COUNT - 1)->queue_delete(); + recent_preset_hbc->get_child(PRESET_COLUMN_COUNT - 1)->queue_free(); } recent_presets.push_back(p_color); recent_preset_cache.push_back(p_color); @@ -770,7 +770,7 @@ void ColorPicker::erase_preset(const Color &p_color) { for (int i = 1; i < preset_container->get_child_count(); i++) { ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); if (current_btn && p_color == current_btn->get_preset_color()) { - current_btn->queue_delete(); + current_btn->queue_free(); break; } } @@ -794,7 +794,7 @@ void ColorPicker::erase_recent_preset(const Color &p_color) { for (int i = 1; i < recent_preset_hbc->get_child_count(); i++) { ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(recent_preset_hbc->get_child(i)); if (current_btn && p_color == current_btn->get_preset_color()) { - current_btn->queue_delete(); + current_btn->queue_free(); break; } } @@ -1087,16 +1087,24 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { Vector<Color> colors; Color col; col.set_ok_hsl(h, s, 1); - points.resize(4); - colors.resize(4); - points.set(0, Vector2()); - points.set(1, Vector2(c->get_size().x, 0)); + Color col2; + col2.set_ok_hsl(h, s, 0.5); + Color col3; + col3.set_ok_hsl(h, s, 0); + points.resize(6); + colors.resize(6); + points.set(0, Vector2(c->get_size().x, 0)); + points.set(1, Vector2(c->get_size().x, c->get_size().y * 0.5)); points.set(2, c->get_size()); points.set(3, Vector2(0, c->get_size().y)); + points.set(4, Vector2(0, c->get_size().y * 0.5)); + points.set(5, Vector2()); colors.set(0, col); - colors.set(1, col); - colors.set(2, Color(0, 0, 0)); - colors.set(3, Color(0, 0, 0)); + colors.set(1, col2); + colors.set(2, col3); + colors.set(3, col3); + colors.set(4, col2); + colors.set(5, col); c->draw_polygon(points, colors); int y = c->get_size().y - c->get_size().y * CLAMP(v, 0, 1); col.set_ok_hsl(h, 1, v); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index c96d3c763d..1167dbec54 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -1087,7 +1087,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o _draw_fbg_boxes(ci, rid, fbg_line_off, it_from, it_to, chr_range.x, chr_range.y, 0); // Draw main text. - Color selection_fg = theme_cache.font_selected_color; Color selection_bg = theme_cache.selection_color; int sel_start = -1; @@ -1276,8 +1275,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } } - if (selected) { - font_color = override_selected_font_color ? selection_fg : font_color; + if (selected && use_selected_font_color) { + font_color = theme_cache.font_selected_color; } // Draw glyphs. @@ -1286,9 +1285,9 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o if (txt_visible) { if (!skip) { if (frid != RID()) { - TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + TS->font_draw_glyph(frid, ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); } else if ((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { - TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, selected ? selection_fg : font_color); + TS->draw_hex_code_box(ci, glyphs[i].font_size, p_ofs + fx_offset + off, gl, font_color); } } r_processed_glyphs++; @@ -1688,6 +1687,7 @@ void RichTextLabel::_update_theme_item_cache() { theme_cache.default_color = get_theme_color(SNAME("default_color")); theme_cache.font_selected_color = get_theme_color(SNAME("font_selected_color")); + use_selected_font_color = theme_cache.font_selected_color != Color(0, 0, 0, 0); theme_cache.selection_color = get_theme_color(SNAME("selection_color")); theme_cache.font_outline_color = get_theme_color(SNAME("font_outline_color")); theme_cache.font_shadow_color = get_theme_color(SNAME("font_shadow_color")); @@ -3564,14 +3564,6 @@ bool RichTextLabel::is_hint_underlined() const { return underline_hint; } -void RichTextLabel::set_override_selected_font_color(bool p_override_selected_font_color) { - override_selected_font_color = p_override_selected_font_color; -} - -bool RichTextLabel::is_overriding_selected_font_color() const { - return override_selected_font_color; -} - void RichTextLabel::set_offset(int p_pixel) { vscroll->set_value(p_pixel); } @@ -5294,9 +5286,6 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("set_hint_underline", "enable"), &RichTextLabel::set_hint_underline); ClassDB::bind_method(D_METHOD("is_hint_underlined"), &RichTextLabel::is_hint_underlined); - ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &RichTextLabel::set_override_selected_font_color); - ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &RichTextLabel::is_overriding_selected_font_color); - ClassDB::bind_method(D_METHOD("set_scroll_active", "active"), &RichTextLabel::set_scroll_active); ClassDB::bind_method(D_METHOD("is_scroll_active"), &RichTextLabel::is_scroll_active); @@ -5406,7 +5395,6 @@ void RichTextLabel::_bind_methods() { ADD_GROUP("Text Selection", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selection_enabled"), "set_selection_enabled", "is_selection_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deselect_on_focus_loss_enabled"), "set_deselect_on_focus_loss_enabled", "is_deselect_on_focus_loss_enabled"); ADD_GROUP("Displayed Text", ""); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 04a682349d..d30baaa8d3 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -396,7 +396,7 @@ private: int tab_size = 4; bool underline_meta = true; bool underline_hint = true; - bool override_selected_font_color = false; + bool use_selected_font_color = false; HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 48853cde9c..144aa2a1ef 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1219,7 +1219,7 @@ void TextEdit::_notification(int p_what) { int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c); int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c); - if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && override_selected_font_color) { + if (glyphs[j].start >= sel_from && glyphs[j].end <= sel_to && use_selected_font_color) { gl_color = font_selected_color; } } @@ -2094,6 +2094,17 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { accept_event(); return; } + + if (k->is_action("ui_text_caret_add_below", true)) { + add_caret_at_carets(true); + accept_event(); + return; + } + if (k->is_action("ui_text_caret_add_above", true)) { + add_caret_at_carets(false); + accept_event(); + return; + } } // MISC. @@ -2808,6 +2819,51 @@ void TextEdit::_move_caret_document_end(bool p_select) { } } +void TextEdit::_get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x) const { + if (p_last_fit_x == -1) { + p_last_fit_x = _get_column_x_offset_for_line(p_old_column, p_old_line, p_old_column); + } + + // Calculate the new line and wrap index + p_new_line = p_old_line; + int caret_wrap_index = p_old_wrap_index; + if (p_below) { + if (caret_wrap_index < get_line_wrap_count(p_new_line)) { + caret_wrap_index++; + } else { + p_new_line++; + caret_wrap_index = 0; + } + } else { + if (caret_wrap_index == 0) { + p_new_line--; + caret_wrap_index = get_line_wrap_count(p_new_line); + } else { + caret_wrap_index--; + } + } + + // Boundary checks + if (p_new_line < 0) { + p_new_line = 0; + } + if (p_new_line >= text.size()) { + p_new_line = text.size() - 1; + } + + p_new_column = _get_char_pos_for_line(p_last_fit_x, p_new_line, caret_wrap_index); + if (p_new_column != 0 && get_line_wrapping_mode() != LineWrappingMode::LINE_WRAPPING_NONE && caret_wrap_index < get_line_wrap_count(p_new_line)) { + Vector<String> rows = get_line_wrapped_text(p_new_line); + int row_end_col = 0; + for (int i = 0; i < caret_wrap_index + 1; i++) { + row_end_col += rows[i].length(); + } + if (p_new_column >= row_end_col) { + p_new_column -= 1; + } + } +} + void TextEdit::_update_placeholder() { if (font.is_null() || font_size <= 0) { return; // Not in tree? @@ -2866,6 +2922,7 @@ void TextEdit::_update_caches() { /* Selection */ font_selected_color = get_theme_color(SNAME("font_selected_color")); selection_color = get_theme_color(SNAME("selection_color")); + use_selected_font_color = font_selected_color != Color(0, 0, 0, 0); /* Visual. */ style_normal = get_theme_stylebox(SNAME("normal")); @@ -3815,6 +3872,9 @@ void TextEdit::undo() { return; } + if (in_action) { + pending_action_end = true; + } _push_current_op(); if (undo_stack_pos == nullptr) { @@ -3876,6 +3936,9 @@ void TextEdit::redo() { return; } + if (in_action) { + pending_action_end = true; + } _push_current_op(); if (undo_stack_pos == nullptr) { @@ -4509,6 +4572,68 @@ int TextEdit::get_caret_count() const { return carets.size(); } +void TextEdit::add_caret_at_carets(bool p_below) { + Vector<int> caret_edit_order = get_caret_index_edit_order(); + for (const int &caret_index : caret_edit_order) { + const int caret_line = get_caret_line(caret_index); + const int caret_column = get_caret_column(caret_index); + + // The last fit x will be cleared if the caret has a selection, + // but if it does not have a selection the last fit x will be + // transferred to the new caret + int caret_from_column = 0, caret_to_column = 0, caret_last_fit_x = carets[caret_index].last_fit_x; + if (has_selection(caret_index)) { + // If the selection goes over multiple lines, deselect it. + if (get_selection_from_line(caret_index) != get_selection_to_line(caret_index)) { + deselect(caret_index); + } else { + caret_from_column = get_selection_from_column(caret_index); + caret_to_column = get_selection_to_column(caret_index); + caret_last_fit_x = -1; + carets.write[caret_index].last_fit_x = _get_column_x_offset_for_line(caret_column, caret_line, caret_column); + } + } + + // Get the line and column of the new caret as if you would move the caret by pressing the arrow keys + int new_caret_line, new_caret_column, new_caret_from_column = 0, new_caret_to_column = 0; + _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_column, p_below, new_caret_line, new_caret_column, caret_last_fit_x); + + // If the caret does have a selection calculate the new from and to columns + if (caret_from_column != caret_to_column) { + // We only need to calculate the selection columns if the column of the caret changed + if (caret_column != new_caret_column) { + int _; // unused placeholder for p_new_line + _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_from_column, p_below, _, new_caret_from_column); + _get_above_below_caret_line_column(caret_line, get_caret_wrap_index(caret_index), caret_to_column, p_below, _, new_caret_to_column); + } else { + new_caret_from_column = caret_from_column; + new_caret_to_column = caret_to_column; + } + } + + // Add the new caret + const int new_caret_index = add_caret(new_caret_line, new_caret_column); + + if (new_caret_index == -1) { + continue; + } + // Also add the selection if there should be one + if (new_caret_from_column != new_caret_to_column) { + select(new_caret_line, new_caret_from_column, new_caret_line, new_caret_to_column, new_caret_index); + // Necessary to properly modify the selection after adding the new caret + carets.write[new_caret_index].selection.selecting_line = new_caret_line; + carets.write[new_caret_index].selection.selecting_column = new_caret_column == new_caret_from_column ? new_caret_to_column : new_caret_from_column; + continue; + } + + // Copy the last fit x over + carets.write[new_caret_index].last_fit_x = carets[caret_index].last_fit_x; + } + + merge_overlapping_carets(); + queue_redraw(); +} + Vector<int> TextEdit::get_caret_index_edit_order() { if (!caret_index_edit_dirty) { return caret_index_edit_order; @@ -4744,14 +4869,6 @@ bool TextEdit::is_drag_and_drop_selection_enabled() const { return drag_and_drop_selection_enabled; } -void TextEdit::set_override_selected_font_color(bool p_override_selected_font_color) { - override_selected_font_color = p_override_selected_font_color; -} - -bool TextEdit::is_overriding_selected_font_color() const { - return override_selected_font_color; -} - void TextEdit::set_selection_mode(SelectionMode p_mode, int p_line, int p_column, int p_caret) { ERR_FAIL_INDEX(p_caret, carets.size()); @@ -6003,6 +6120,7 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_secondary_carets"), &TextEdit::remove_secondary_carets); ClassDB::bind_method(D_METHOD("merge_overlapping_carets"), &TextEdit::merge_overlapping_carets); ClassDB::bind_method(D_METHOD("get_caret_count"), &TextEdit::get_caret_count); + ClassDB::bind_method(D_METHOD("add_caret_at_carets", "below"), &TextEdit::add_caret_at_carets); ClassDB::bind_method(D_METHOD("get_caret_index_edit_order"), &TextEdit::get_caret_index_edit_order); ClassDB::bind_method(D_METHOD("adjust_carets_after_edit", "caret", "from_line", "from_col", "to_line", "to_col"), &TextEdit::adjust_carets_after_edit); @@ -6036,9 +6154,6 @@ void TextEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_drag_and_drop_selection_enabled", "enable"), &TextEdit::set_drag_and_drop_selection_enabled); ClassDB::bind_method(D_METHOD("is_drag_and_drop_selection_enabled"), &TextEdit::is_drag_and_drop_selection_enabled); - ClassDB::bind_method(D_METHOD("set_override_selected_font_color", "override"), &TextEdit::set_override_selected_font_color); - ClassDB::bind_method(D_METHOD("is_overriding_selected_font_color"), &TextEdit::is_overriding_selected_font_color); - ClassDB::bind_method(D_METHOD("set_selection_mode", "mode", "line", "column", "caret_index"), &TextEdit::set_selection_mode, DEFVAL(-1), DEFVAL(-1), DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_selection_mode"), &TextEdit::get_selection_mode); @@ -6208,7 +6323,6 @@ void TextEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "middle_mouse_paste_enabled"), "set_middle_mouse_paste_enabled", "is_middle_mouse_paste_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "wrap_mode", PROPERTY_HINT_ENUM, "None,Boundary"), "set_line_wrapping_mode", "get_line_wrapping_mode"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "override_selected_font_color"), "set_override_selected_font_color", "is_overriding_selected_font_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_current_line"), "set_highlight_current_line", "is_highlight_current_line_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_control_chars"), "set_draw_control_chars", "get_draw_control_chars"); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 86b838e387..935f2a7ce8 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -443,9 +443,9 @@ private: bool deselect_on_focus_loss_enabled = true; bool drag_and_drop_selection_enabled = true; - Color font_selected_color = Color(1, 1, 1); + Color font_selected_color = Color(0, 0, 0, 0); Color selection_color = Color(1, 1, 1); - bool override_selected_font_color = false; + bool use_selected_font_color = false; bool selection_drag_attempt = false; bool dragging_selection = false; @@ -598,6 +598,9 @@ private: void _move_caret_document_start(bool p_select); void _move_caret_document_end(bool p_select); + // Used in add_caret_at_carets + void _get_above_below_caret_line_column(int p_old_line, int p_old_wrap_index, int p_old_column, bool p_below, int &p_new_line, int &p_new_column, int p_last_fit_x = -1) const; + protected: void _notification(int p_what); @@ -816,6 +819,7 @@ public: void remove_secondary_carets(); void merge_overlapping_carets(); int get_caret_count() const; + void add_caret_at_carets(bool p_below); Vector<int> get_caret_index_edit_order(); void adjust_carets_after_edit(int p_caret, int p_from_line, int p_from_col, int p_to_line, int p_to_col); diff --git a/scene/gui/texture_progress_bar.cpp b/scene/gui/texture_progress_bar.cpp index a9982b3ece..48c6dc5cfc 100644 --- a/scene/gui/texture_progress_bar.cpp +++ b/scene/gui/texture_progress_bar.cpp @@ -510,18 +510,38 @@ void TextureProgressBar::_notification(int p_what) { } pts.append(to); + Ref<AtlasTexture> atlas_progress = progress; + bool valid_atlas_progress = atlas_progress.is_valid() && atlas_progress->get_atlas().is_valid(); + Rect2 region_rect; + Size2 atlas_size; + if (valid_atlas_progress) { + region_rect = atlas_progress->get_region(); + atlas_size = atlas_progress->get_atlas()->get_size(); + } + Vector<Point2> uvs; Vector<Point2> points; - uvs.push_back(get_relative_center()); - points.push_back(progress_offset + s * get_relative_center()); for (int i = 0; i < pts.size(); i++) { Point2 uv = unit_val_to_uv(pts[i]); if (uvs.find(uv) >= 0) { continue; } - uvs.push_back(uv); points.push_back(progress_offset + Point2(uv.x * s.x, uv.y * s.y)); + if (valid_atlas_progress) { + uv.x = Math::remap(uv.x, 0, 1, region_rect.position.x / atlas_size.x, (region_rect.position.x + region_rect.size.x) / atlas_size.x); + uv.y = Math::remap(uv.y, 0, 1, region_rect.position.y / atlas_size.y, (region_rect.position.y + region_rect.size.y) / atlas_size.y); + } + uvs.push_back(uv); + } + + Point2 center_point = get_relative_center(); + points.push_back(progress_offset + s * center_point); + if (valid_atlas_progress) { + center_point.x = Math::remap(center_point.x, 0, 1, region_rect.position.x / atlas_size.x, (region_rect.position.x + region_rect.size.x) / atlas_size.x); + center_point.y = Math::remap(center_point.y, 0, 1, region_rect.position.y / atlas_size.y, (region_rect.position.y + region_rect.size.y) / atlas_size.y); } + uvs.push_back(center_point); + Vector<Color> colors; colors.push_back(tint_progress); draw_polygon(points, colors, uvs, progress); diff --git a/scene/main/instance_placeholder.cpp b/scene/main/instance_placeholder.cpp index 6dd83e4636..56e719968b 100644 --- a/scene/main/instance_placeholder.cpp +++ b/scene/main/instance_placeholder.cpp @@ -100,7 +100,7 @@ Node *InstancePlaceholder::create_instance(bool p_replace, const Ref<PackedScene } if (p_replace) { - queue_delete(); + queue_free(); base->remove_child(this); } diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 2ea45df309..038f182e8b 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -1341,12 +1341,23 @@ Node *Node::get_node(const NodePath &p_path) const { Node *node = get_node_or_null(p_path); if (unlikely(!node)) { + // Try to get a clear description of this node in the error message. + String desc; + if (is_inside_tree()) { + desc = get_path(); + } else { + desc = get_name(); + if (desc.is_empty()) { + desc = get_class(); + } + } + if (p_path.is_absolute()) { ERR_FAIL_V_MSG(nullptr, - vformat(R"(Node not found: "%s" (absolute path attempted from "%s").)", p_path, get_path())); + vformat(R"(Node not found: "%s" (absolute path attempted from "%s").)", p_path, desc)); } else { ERR_FAIL_V_MSG(nullptr, - vformat(R"(Node not found: "%s" (relative to "%s").)", p_path, get_path())); + vformat(R"(Node not found: "%s" (relative to "%s").)", p_path, desc)); } } @@ -2569,7 +2580,7 @@ void Node::print_orphan_nodes() { #endif } -void Node::queue_delete() { +void Node::queue_free() { if (is_inside_tree()) { get_tree()->queue_delete(this); } else { @@ -2811,7 +2822,7 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("get_viewport"), &Node::get_viewport); - ClassDB::bind_method(D_METHOD("queue_free"), &Node::queue_delete); + ClassDB::bind_method(D_METHOD("queue_free"), &Node::queue_free); ClassDB::bind_method(D_METHOD("request_ready"), &Node::request_ready); diff --git a/scene/main/node.h b/scene/main/node.h index c8c8c395ce..4a3ec253b1 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -460,7 +460,7 @@ public: #endif static String adjust_name_casing(const String &p_name); - void queue_delete(); + void queue_free(); //hacks for speed static void init_node_hrcr(); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index ad40346c36..02bde19201 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1161,7 +1161,7 @@ void Viewport::_gui_cancel_tooltip() { gui.tooltip_timer = Ref<SceneTreeTimer>(); } if (gui.tooltip_popup) { - gui.tooltip_popup->queue_delete(); + gui.tooltip_popup->queue_free(); } } diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp index a9b7e9acbe..f445634a45 100644 --- a/scene/property_utils.cpp +++ b/scene/property_utils.cpp @@ -169,8 +169,10 @@ static bool _collect_inheritance_chain(const Ref<SceneState> &p_state, const Nod state = state->get_base_scene_state(); } - for (int i = inheritance_states.size() - 1; i >= 0; --i) { - r_states_stack.push_back(inheritance_states[i]); + if (inheritance_states.size() > 0) { + for (int i = inheritance_states.size() - 1; i >= 0; --i) { + r_states_stack.push_back(inheritance_states[i]); + } } return found; @@ -214,10 +216,12 @@ Vector<SceneState::PackState> PropertyUtils::get_node_states_stack(const Node *p { states_stack_ret.resize(states_stack.size()); _FastPackState *ps = states_stack.ptr(); - for (int i = states_stack.size() - 1; i >= 0; --i) { - states_stack_ret.write[i].state.reference_ptr(ps->state); - states_stack_ret.write[i].node = ps->node; - ++ps; + if (states_stack.size() > 0) { + for (int i = states_stack.size() - 1; i >= 0; --i) { + states_stack_ret.write[i].state.reference_ptr(ps->state); + states_stack_ret.write[i].node = ps->node; + ++ps; + } } } return states_stack_ret; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index c9d92cea3f..7f51903586 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -433,7 +433,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("background_color", "TextEdit", Color(0, 0, 0, 0)); theme->set_color("font_color", "TextEdit", control_font_color); - theme->set_color("font_selected_color", "TextEdit", control_font_pressed_color); + theme->set_color("font_selected_color", "TextEdit", Color(0, 0, 0, 0)); theme->set_color("font_readonly_color", "TextEdit", control_font_disabled_color); theme->set_color("font_placeholder_color", "TextEdit", control_font_placeholder_color); theme->set_color("font_outline_color", "TextEdit", Color(1, 1, 1)); @@ -476,7 +476,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("completion_scroll_hovered_color", "CodeEdit", control_font_pressed_color * Color(1, 1, 1, 0.4)); theme->set_color("completion_font_color", "CodeEdit", Color(0.67, 0.67, 0.67)); theme->set_color("font_color", "CodeEdit", control_font_color); - theme->set_color("font_selected_color", "CodeEdit", Color(0, 0, 0)); + theme->set_color("font_selected_color", "CodeEdit", Color(0, 0, 0, 0)); theme->set_color("font_readonly_color", "CodeEdit", Color(control_font_color.r, control_font_color.g, control_font_color.b, 0.5f)); theme->set_color("font_placeholder_color", "CodeEdit", control_font_placeholder_color); theme->set_color("font_outline_color", "CodeEdit", Color(1, 1, 1)); @@ -959,7 +959,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_font_size("mono_font_size", "RichTextLabel", -1); theme->set_color("default_color", "RichTextLabel", Color(1, 1, 1)); - theme->set_color("font_selected_color", "RichTextLabel", Color(0, 0, 0)); + theme->set_color("font_selected_color", "RichTextLabel", Color(0, 0, 0, 0)); theme->set_color("selection_color", "RichTextLabel", Color(0.1, 0.1, 1, 0.8)); theme->set_color("font_shadow_color", "RichTextLabel", Color(0, 0, 0, 0)); diff --git a/servers/rendering/renderer_rd/shaders/cluster_render.glsl b/servers/rendering/renderer_rd/shaders/cluster_render.glsl index 2fe230f0bf..932312de82 100644 --- a/servers/rendering/renderer_rd/shaders/cluster_render.glsl +++ b/servers/rendering/renderer_rd/shaders/cluster_render.glsl @@ -64,7 +64,7 @@ void main() { #version 450 #VERSION_DEFINES - +#ifndef MOLTENVK_USED // Metal will corrupt GPU state otherwise #if defined(has_GL_KHR_shader_subgroup_ballot) && defined(has_GL_KHR_shader_subgroup_arithmetic) && defined(has_GL_KHR_shader_subgroup_vote) #extension GL_KHR_shader_subgroup_ballot : enable @@ -73,6 +73,7 @@ void main() { #define USE_SUBGROUPS #endif +#endif layout(location = 0) in float depth_interp; layout(location = 1) in flat uint element_index; diff --git a/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl b/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl index b0a0839836..02566d8e35 100644 --- a/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl @@ -32,7 +32,9 @@ // Based on Spartan Engine's TAA implementation (without TAA upscale). // <https://github.com/PanosK92/SpartanEngine/blob/a8338d0609b85dc32f3732a5c27fb4463816a3b9/Data/shaders/temporal_antialiasing.hlsl> +#ifndef MOLTENVK_USED #define USE_SUBGROUPS +#endif // MOLTENVK_USED #define GROUP_SIZE 8 #define FLT_MIN 0.00000001 diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl index 293532bf2d..0bdf0e50aa 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl @@ -97,9 +97,7 @@ layout(location = 8) out vec4 prev_screen_position; #ifdef MATERIAL_UNIFORMS_USED layout(set = MATERIAL_UNIFORM_SET, binding = 0, std140) uniform MaterialUniforms{ - #MATERIAL_UNIFORMS - } material; #endif @@ -691,7 +689,7 @@ vec4 fog_process(vec3 vertex) { void cluster_get_item_range(uint p_offset, out uint item_min, out uint item_max, out uint item_from, out uint item_to) { uint item_min_max = cluster_buffer.data[p_offset]; - item_min = item_min_max & 0xFFFF; + item_min = item_min_max & 0xFFFFu; item_max = item_min_max >> 16; item_from = item_min >> 5; @@ -958,9 +956,9 @@ void fragment_shader(in SceneData scene_data) { while (merged_mask != 0) { uint bit = findMSB(merged_mask); - merged_mask &= ~(1 << bit); + merged_mask &= ~(1u << bit); #ifdef USE_SUBGROUPS - if (((1 << bit) & mask) == 0) { //do not process if not originally here + if (((1u << bit) & mask) == 0) { //do not process if not originally here continue; } #endif @@ -1419,9 +1417,9 @@ void fragment_shader(in SceneData scene_data) { while (merged_mask != 0) { uint bit = findMSB(merged_mask); - merged_mask &= ~(1 << bit); + merged_mask &= ~(1u << bit); #ifdef USE_SUBGROUPS - if (((1 << bit) & mask) == 0) { //do not process if not originally here + if (((1u << bit) & mask) == 0) { //do not process if not originally here continue; } #endif @@ -1781,9 +1779,9 @@ void fragment_shader(in SceneData scene_data) { float shadow = 1.0; #ifndef SHADOWS_DISABLED if (i < 4) { - shadow = float(shadow0 >> (i * 8) & 0xFF) / 255.0; + shadow = float(shadow0 >> (i * 8u) & 0xFFu) / 255.0; } else { - shadow = float(shadow1 >> ((i - 4) * 8) & 0xFF) / 255.0; + shadow = float(shadow1 >> ((i - 4u) * 8u) & 0xFFu) / 255.0; } shadow = shadow * directional_lights.data[i].shadow_opacity + 1.0 - directional_lights.data[i].shadow_opacity; @@ -1845,9 +1843,9 @@ void fragment_shader(in SceneData scene_data) { while (merged_mask != 0) { uint bit = findMSB(merged_mask); - merged_mask &= ~(1 << bit); + merged_mask &= ~(1u << bit); #ifdef USE_SUBGROUPS - if (((1 << bit) & mask) == 0) { //do not process if not originally here + if (((1u << bit) & mask) == 0) { //do not process if not originally here continue; } #endif @@ -1916,9 +1914,9 @@ void fragment_shader(in SceneData scene_data) { while (merged_mask != 0) { uint bit = findMSB(merged_mask); - merged_mask &= ~(1 << bit); + merged_mask &= ~(1u << bit); #ifdef USE_SUBGROUPS - if (((1 << bit) & mask) == 0) { //do not process if not originally here + if (((1u << bit) & mask) == 0) { //do not process if not originally here continue; } #endif @@ -2071,7 +2069,7 @@ void fragment_shader(in SceneData scene_data) { float sGreen = floor((cGreen / pow(2.0f, exps - B - N)) + 0.5f); float sBlue = floor((cBlue / pow(2.0f, exps - B - N)) + 0.5f); //store as 8985 to have 2 extra neighbour bits - uint light_rgbe = ((uint(sRed) & 0x1FF) >> 1) | ((uint(sGreen) & 0x1FF) << 8) | (((uint(sBlue) & 0x1FF) >> 1) << 17) | ((uint(exps) & 0x1F) << 25); + uint light_rgbe = ((uint(sRed) & 0x1FFu) >> 1) | ((uint(sGreen) & 0x1FFu) << 8) | (((uint(sBlue) & 0x1FFu) >> 1) << 17) | ((uint(exps) & 0x1Fu) << 25); imageStore(emission_grid, grid_pos, uvec4(light_rgbe)); imageStore(emission_aniso_grid, grid_pos, uvec4(light_aniso)); @@ -2105,8 +2103,8 @@ void fragment_shader(in SceneData scene_data) { if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_VOXEL_GI)) { // process voxel_gi_instances uint index1 = instances.data[instance_index].gi_offset & 0xFFFF; uint index2 = instances.data[instance_index].gi_offset >> 16; - voxel_gi_buffer.x = index1 & 0xFF; - voxel_gi_buffer.y = index2 & 0xFF; + voxel_gi_buffer.x = index1 & 0xFFu; + voxel_gi_buffer.y = index2 & 0xFFu; } else { voxel_gi_buffer.x = 0xFF; voxel_gi_buffer.y = 0xFF; diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl index 0d36b98645..3a45ab0059 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl @@ -4,14 +4,15 @@ #define MAX_VOXEL_GI_INSTANCES 8 #define MAX_VIEWS 2 +#ifndef MOLTENVK_USED #if defined(has_GL_KHR_shader_subgroup_ballot) && defined(has_GL_KHR_shader_subgroup_arithmetic) #extension GL_KHR_shader_subgroup_ballot : enable #extension GL_KHR_shader_subgroup_arithmetic : enable #define USE_SUBGROUPS - #endif +#endif // MOLTENVK_USED #if defined(USE_MULTIVIEW) && defined(has_VK_KHR_multiview) #extension GL_EXT_multiview : enable diff --git a/tests/core/math/test_basis.h b/tests/core/math/test_basis.h index 90ca9e439f..f52b715cd7 100644 --- a/tests/core/math/test_basis.h +++ b/tests/core/math/test_basis.h @@ -38,15 +38,6 @@ namespace TestBasis { -enum RotOrder { - EulerXYZ, - EulerXZY, - EulerYZX, - EulerYXZ, - EulerZXY, - EulerZYX -}; - Vector3 deg_to_rad(const Vector3 &p_rotation) { return p_rotation / 180.0 * Math_PI; } @@ -55,88 +46,26 @@ Vector3 rad2deg(const Vector3 &p_rotation) { return p_rotation / Math_PI * 180.0; } -Basis EulerToBasis(RotOrder mode, const Vector3 &p_rotation) { - Basis ret; - switch (mode) { - case EulerXYZ: - ret.set_euler(p_rotation, Basis::EULER_ORDER_XYZ); - break; - - case EulerXZY: - ret.set_euler(p_rotation, Basis::EULER_ORDER_XZY); - break; - - case EulerYZX: - ret.set_euler(p_rotation, Basis::EULER_ORDER_YZX); - break; - - case EulerYXZ: - ret.set_euler(p_rotation, Basis::EULER_ORDER_YXZ); - break; - - case EulerZXY: - ret.set_euler(p_rotation, Basis::EULER_ORDER_ZXY); - break; - - case EulerZYX: - ret.set_euler(p_rotation, Basis::EULER_ORDER_ZYX); - break; - - default: - // If you land here, Please integrate all rotation orders. - FAIL("This is not unreachable."); - } - - return ret; -} - -Vector3 BasisToEuler(RotOrder mode, const Basis &p_rotation) { - switch (mode) { - case EulerXYZ: - return p_rotation.get_euler(Basis::EULER_ORDER_XYZ); - - case EulerXZY: - return p_rotation.get_euler(Basis::EULER_ORDER_XZY); - - case EulerYZX: - return p_rotation.get_euler(Basis::EULER_ORDER_YZX); - - case EulerYXZ: - return p_rotation.get_euler(Basis::EULER_ORDER_YXZ); - - case EulerZXY: - return p_rotation.get_euler(Basis::EULER_ORDER_ZXY); - - case EulerZYX: - return p_rotation.get_euler(Basis::EULER_ORDER_ZYX); - - default: - // If you land here, Please integrate all rotation orders. - FAIL("This is not unreachable."); - return Vector3(); - } -} - -String get_rot_order_name(RotOrder ro) { +String get_rot_order_name(Basis::EulerOrder ro) { switch (ro) { - case EulerXYZ: + case Basis::EULER_ORDER_XYZ: return "XYZ"; - case EulerXZY: + case Basis::EULER_ORDER_XZY: return "XZY"; - case EulerYZX: + case Basis::EULER_ORDER_YZX: return "YZX"; - case EulerYXZ: + case Basis::EULER_ORDER_YXZ: return "YXZ"; - case EulerZXY: + case Basis::EULER_ORDER_ZXY: return "ZXY"; - case EulerZYX: + case Basis::EULER_ORDER_ZYX: return "ZYX"; default: return "[Not supported]"; } } -void test_rotation(Vector3 deg_original_euler, RotOrder rot_order) { +void test_rotation(Vector3 deg_original_euler, Basis::EulerOrder rot_order) { // This test: // 1. Converts the rotation vector from deg to rad. // 2. Converts euler to basis. @@ -156,11 +85,11 @@ void test_rotation(Vector3 deg_original_euler, RotOrder rot_order) { // Euler to rotation const Vector3 original_euler = deg_to_rad(deg_original_euler); - const Basis to_rotation = EulerToBasis(rot_order, original_euler); + const Basis to_rotation = Basis::from_euler(original_euler, rot_order); // Euler from rotation - const Vector3 euler_from_rotation = BasisToEuler(rot_order, to_rotation); - const Basis rotation_from_computed_euler = EulerToBasis(rot_order, euler_from_rotation); + const Vector3 euler_from_rotation = to_rotation.get_euler(rot_order); + const Basis rotation_from_computed_euler = Basis::from_euler(euler_from_rotation, rot_order); Basis res = to_rotation.inverse() * rotation_from_computed_euler; @@ -184,13 +113,13 @@ void test_rotation(Vector3 deg_original_euler, RotOrder rot_order) { } TEST_CASE("[Basis] Euler conversions") { - Vector<RotOrder> rotorder_to_test; - rotorder_to_test.push_back(EulerXYZ); - rotorder_to_test.push_back(EulerXZY); - rotorder_to_test.push_back(EulerYZX); - rotorder_to_test.push_back(EulerYXZ); - rotorder_to_test.push_back(EulerZXY); - rotorder_to_test.push_back(EulerZYX); + Vector<Basis::EulerOrder> euler_order_to_test; + euler_order_to_test.push_back(Basis::EULER_ORDER_XYZ); + euler_order_to_test.push_back(Basis::EULER_ORDER_XZY); + euler_order_to_test.push_back(Basis::EULER_ORDER_YZX); + euler_order_to_test.push_back(Basis::EULER_ORDER_YXZ); + euler_order_to_test.push_back(Basis::EULER_ORDER_ZXY); + euler_order_to_test.push_back(Basis::EULER_ORDER_ZYX); Vector<Vector3> vectors_to_test; @@ -248,21 +177,21 @@ TEST_CASE("[Basis] Euler conversions") { vectors_to_test.push_back(Vector3(120.0, 150.0, -130.0)); vectors_to_test.push_back(Vector3(120.0, 150.0, 130.0)); - for (int h = 0; h < rotorder_to_test.size(); h += 1) { + for (int h = 0; h < euler_order_to_test.size(); h += 1) { for (int i = 0; i < vectors_to_test.size(); i += 1) { - test_rotation(vectors_to_test[i], rotorder_to_test[h]); + test_rotation(vectors_to_test[i], euler_order_to_test[h]); } } } TEST_CASE("[Stress][Basis] Euler conversions") { - Vector<RotOrder> rotorder_to_test; - rotorder_to_test.push_back(EulerXYZ); - rotorder_to_test.push_back(EulerXZY); - rotorder_to_test.push_back(EulerYZX); - rotorder_to_test.push_back(EulerYXZ); - rotorder_to_test.push_back(EulerZXY); - rotorder_to_test.push_back(EulerZYX); + Vector<Basis::EulerOrder> euler_order_to_test; + euler_order_to_test.push_back(Basis::EULER_ORDER_XYZ); + euler_order_to_test.push_back(Basis::EULER_ORDER_XZY); + euler_order_to_test.push_back(Basis::EULER_ORDER_YZX); + euler_order_to_test.push_back(Basis::EULER_ORDER_YXZ); + euler_order_to_test.push_back(Basis::EULER_ORDER_ZXY); + euler_order_to_test.push_back(Basis::EULER_ORDER_ZYX); Vector<Vector3> vectors_to_test; // Add 1000 random vectors with weirds numbers. @@ -274,9 +203,9 @@ TEST_CASE("[Stress][Basis] Euler conversions") { rng.randf_range(-1800, 1800))); } - for (int h = 0; h < rotorder_to_test.size(); h += 1) { + for (int h = 0; h < euler_order_to_test.size(); h += 1) { for (int i = 0; i < vectors_to_test.size(); i += 1) { - test_rotation(vectors_to_test[i], rotorder_to_test[h]); + test_rotation(vectors_to_test[i], euler_order_to_test[h]); } } } diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 4b92b9daff..652cbed6e8 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -3353,7 +3353,7 @@ TEST_CASE("[SceneTree][TextEdit] caret") { memdelete(text_edit); } -TEST_CASE("[SceneTree][TextEdit] muiticaret") { +TEST_CASE("[SceneTree][TextEdit] multicaret") { TextEdit *text_edit = memnew(TextEdit); SceneTree::get_singleton()->get_root()->add_child(text_edit); text_edit->set_multiple_carets_enabled(true); @@ -3443,6 +3443,43 @@ TEST_CASE("[SceneTree][TextEdit] muiticaret") { CHECK(text_edit->get_caret_index_edit_order() == caret_index_get_order); } + SUBCASE("[TextEdit] add caret at carets") { + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(1); + text_edit->set_caret_column(9); + + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->get_caret_line(1) == 2); + CHECK(text_edit->get_caret_column(1) == 4); + + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 2); + + text_edit->add_caret_at_carets(false); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->get_caret_line(2) == 0); + CHECK(text_edit->get_caret_column(2) == 7); + + text_edit->remove_secondary_carets(); + text_edit->set_caret_line(0); + text_edit->set_caret_column(4); + text_edit->select(0, 0, 0, 4); + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 2); + CHECK(text_edit->get_selection_from_line(1) == 1); + CHECK(text_edit->get_selection_to_line(1) == 1); + CHECK(text_edit->get_selection_from_column(1) == 0); + CHECK(text_edit->get_selection_to_column(1) == 3); + + text_edit->add_caret_at_carets(true); + CHECK(text_edit->get_caret_count() == 3); + CHECK(text_edit->get_selection_from_line(2) == 2); + CHECK(text_edit->get_selection_to_line(2) == 2); + CHECK(text_edit->get_selection_from_column(2) == 0); + CHECK(text_edit->get_selection_to_column(2) == 4); + } + memdelete(text_edit); } |