diff options
35 files changed, 479 insertions, 272 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index e8c885162b..1bfb745662 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -283,6 +283,7 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { for (int i = 0; i < custom_feature_array.size(); i++) { custom_features.insert(custom_feature_array[i]); } + _queue_changed(); return true; } @@ -324,6 +325,7 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { } } + _queue_changed(); return true; } @@ -424,6 +426,22 @@ void ProjectSettings::_get_property_list(List<PropertyInfo> *p_list) const { } } +void ProjectSettings::_queue_changed() { + if (is_changed || !MessageQueue::get_singleton() || MessageQueue::get_singleton()->get_max_buffer_usage() == 0) { + return; + } + is_changed = true; + callable_mp(this, &ProjectSettings::_emit_changed).call_deferred(); +} + +void ProjectSettings::_emit_changed() { + if (!is_changed) { + return; + } + is_changed = false; + emit_signal("settings_changed"); +} + bool ProjectSettings::_load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset) { if (PackedData::get_singleton()->is_disabled()) { return false; @@ -1225,6 +1243,8 @@ void ProjectSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::_load_resource_pack, DEFVAL(true), DEFVAL(0)); ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd); + + ADD_SIGNAL(MethodInfo("settings_changed")); } void ProjectSettings::_add_builtin_input_map() { @@ -1329,6 +1349,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/mode", PROPERTY_HINT_ENUM, "disabled,canvas_items,viewport"), "disabled"); GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/aspect", PROPERTY_HINT_ENUM, "ignore,keep,keep_width,keep_height,expand"), "keep"); GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "display/window/stretch/scale", PROPERTY_HINT_RANGE, "0.5,8.0,0.01"), 1.0); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/scale_mode", PROPERTY_HINT_ENUM, "fractional,integer"), "fractional"); GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/profiler/max_functions", PROPERTY_HINT_RANGE, "128,65535,1"), 16384); diff --git a/core/config/project_settings.h b/core/config/project_settings.h index a1f52fa1e0..dba4aa6822 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -45,6 +45,8 @@ class ProjectSettings : public Object { _THREAD_SAFE_CLASS_ friend class TestProjectSettingsInternalsAccessor; + bool is_changed = false; + public: typedef HashMap<String, Variant> CustomMap; static const String PROJECT_DATA_DIR_NAME_SUFFIX; @@ -115,6 +117,9 @@ protected: bool _property_can_revert(const StringName &p_name) const; bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + void _queue_changed(); + void _emit_changed(); + static ProjectSettings *singleton; Error _load_settings_text(const String &p_path); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 008452a92e..e44fdd9776 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1463,6 +1463,10 @@ <member name="DisplayServer" type="DisplayServer" setter="" getter=""> The [DisplayServer] singleton. </member> + <member name="EditorInterface" type="EditorInterface" setter="" getter=""> + The [EditorInterface] singleton. + [b]Note:[/b] Only available in editor builds. + </member> <member name="Engine" type="Engine" setter="" getter=""> The [Engine] singleton. </member> diff --git a/doc/classes/EditorCommandPalette.xml b/doc/classes/EditorCommandPalette.xml index df8bec8002..e6af82b1c3 100644 --- a/doc/classes/EditorCommandPalette.xml +++ b/doc/classes/EditorCommandPalette.xml @@ -8,13 +8,13 @@ Command key names use slash delimiters to distinguish sections, for example: [code]"example/command1"[/code] then [code]example[/code] will be the section name. [codeblocks] [gdscript] - var command_palette = get_editor_interface().get_command_palette() + var command_palette = EditorInterface.get_command_palette() # external_command is a function that will be called with the command is executed. var command_callable = Callable(self, "external_command").bind(arguments) command_palette.add_command("command", "test/command",command_callable) [/gdscript] [csharp] - EditorCommandPalette commandPalette = GetEditorInterface().GetCommandPalette(); + EditorCommandPalette commandPalette = EditorInterface.Singleton.GetCommandPalette(); // ExternalCommand is a function that will be called with the command is executed. Callable commandCallable = new Callable(this, MethodName.ExternalCommand); commandPalette.AddCommand("command", "test/command", commandCallable) diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index cabd9c0da6..3ae4b7f812 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -5,7 +5,16 @@ </brief_description> <description> [EditorInterface] gives you control over Godot editor's window. It allows customizing the window, saving and (re-)loading scenes, rendering mesh previews, inspecting and editing resources and objects, and provides access to [EditorSettings], [EditorFileSystem], [EditorResourcePreview], [ScriptEditor], the editor viewport, and information about scenes. - [b]Note:[/b] This class shouldn't be instantiated directly. Instead, access the singleton using [method EditorPlugin.get_editor_interface]. + [b]Note:[/b] This class shouldn't be instantiated directly. Instead, access the singleton directly by its name. + [codeblocks] + [gdscript] + var editor_settings = EditorInterface.get_editor_settings() + [/gdscript] + [csharp] + // In C# you can access it via the static Singleton property. + EditorSettings settings = EditorInterface.Singleton.GetEditorSettings(); + [/csharp] + [/codeblocks] </description> <tutorials> </tutorials> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index ffb4df25d3..8d280b8276 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -245,7 +245,7 @@ # You can use a custom icon: return preload("res://addons/my_plugin/my_plugin_icon.svg") # Or use a built-in icon: - return get_editor_interface().get_base_control().get_theme_icon("Node", "EditorIcons") + return EditorInterface.get_base_control().get_theme_icon("Node", "EditorIcons") [/gdscript] [csharp] public override Texture2D _GetPluginIcon() @@ -253,7 +253,7 @@ // You can use a custom icon: return ResourceLoader.Load<Texture2D>("res://addons/my_plugin/my_plugin_icon.svg"); // Or use a built-in icon: - return GetEditorInterface().GetBaseControl().GetThemeIcon("Node", "EditorIcons"); + return EditorInterface.Singleton.GetBaseControl().GetThemeIcon("Node", "EditorIcons"); } [/csharp] [/codeblocks] @@ -340,7 +340,7 @@ func _enter_tree(): plugin_control = preload("my_plugin_control.tscn").instantiate() - get_editor_interface().get_editor_main_screen().add_child(plugin_control) + EditorInterface.get_editor_main_screen().add_child(plugin_control) plugin_control.hide() func _has_main_screen(): @@ -353,7 +353,7 @@ return "My Super Cool Plugin 3000" func _get_plugin_icon(): - return get_editor_interface().get_base_control().get_theme_icon("Node", "EditorIcons") + return EditorInterface.get_base_control().get_theme_icon("Node", "EditorIcons") [/codeblock] </description> </method> @@ -558,10 +558,11 @@ The callback should have 4 arguments: [Object] [code]undo_redo[/code], [Object] [code]modified_object[/code], [String] [code]property[/code] and [Variant] [code]new_value[/code]. They are, respectively, the [UndoRedo] object used by the inspector, the currently modified object, the name of the modified property and the new value the property is about to take. </description> </method> - <method name="get_editor_interface"> + <method name="get_editor_interface" is_deprecated="true"> <return type="EditorInterface" /> <description> - Returns the [EditorInterface] singleton. It provides access to some parts of the editor GUI as well as various inner states and tools. + Returns the [EditorInterface] singleton instance. + [i]Deprecated.[/i] [EditorInterface] is a global singleton and can be accessed directly by its name. </description> </method> <method name="get_export_as_menu"> diff --git a/doc/classes/EditorScript.xml b/doc/classes/EditorScript.xml index 01e6b9a52e..8033c18918 100644 --- a/doc/classes/EditorScript.xml +++ b/doc/classes/EditorScript.xml @@ -48,10 +48,11 @@ [b]Warning:[/b] The implementation of this method is currently disabled. </description> </method> - <method name="get_editor_interface" qualifiers="const"> + <method name="get_editor_interface" qualifiers="const" is_deprecated="true"> <return type="EditorInterface" /> <description> Returns the [EditorInterface] singleton instance. + [i]Deprecated.[/i] [EditorInterface] is a global singleton and can be accessed directly by its name. </description> </method> <method name="get_scene" qualifiers="const"> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 85efe4362e..5ca89dc03e 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -9,7 +9,7 @@ Accessing the settings can be done using the following methods, such as: [codeblocks] [gdscript] - var settings = get_editor_interface().get_editor_settings() + var settings = EditorInterface.get_editor_settings() # `settings.set("some/property", 10)` also works as this class overrides `_set()` internally. settings.set_setting("some/property", 10) # `settings.get("some/property")` also works as this class overrides `_get()` internally. @@ -17,7 +17,7 @@ var list_of_settings = settings.get_property_list() [/gdscript] [csharp] - EditorSettings settings = GetEditorInterface().GetEditorSettings(); + EditorSettings settings = EditorInterface.Singleton.GetEditorSettings(); // `settings.set("some/property", value)` also works as this class overrides `_set()` internally. settings.SetSetting("some/property", Value); // `settings.get("some/property", value)` also works as this class overrides `_get()` internally. diff --git a/doc/classes/NavigationAgent2D.xml b/doc/classes/NavigationAgent2D.xml index c1570f3149..71f8c50667 100644 --- a/doc/classes/NavigationAgent2D.xml +++ b/doc/classes/NavigationAgent2D.xml @@ -180,7 +180,7 @@ The distance to search for other agents. </member> <member name="path_desired_distance" type="float" setter="set_path_desired_distance" getter="get_path_desired_distance" default="20.0"> - The distance threshold before a path point is considered to be reached. This allows agents to not have to hit a path point on the path exactly, but only to reach its general area. If this value is set too high, the NavigationAgent will skip points on the path, which can lead too leaving the navigation mesh. If this value is set too low, the NavigationAgent will be stuck in a repath loop because it will constantly overshoot or undershoot the distance to the next point on each physics frame update. + The distance threshold before a path point is considered to be reached. This allows agents to not have to hit a path point on the path exactly, but only to reach its general area. If this value is set too high, the NavigationAgent will skip points on the path, which can lead to leaving the navigation mesh. If this value is set too low, the NavigationAgent will be stuck in a repath loop because it will constantly overshoot or undershoot the distance to the next point on each physics frame update. </member> <member name="path_max_distance" type="float" setter="set_path_max_distance" getter="get_path_max_distance" default="100.0"> The maximum distance the agent is allowed away from the ideal path to the final position. This can happen due to trying to avoid collisions. When the maximum distance is exceeded, it recalculates the ideal path. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 40a475851d..3eb1959a44 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -813,6 +813,10 @@ [b]"viewport"[/b]: The size of the root [Viewport] is set precisely to the base size specified in the Project Settings' Display section. The scene is rendered to this viewport first. Finally, this viewport is scaled to fit the screen (taking [member display/window/stretch/aspect] into account). Recommended for games that use a pixel art esthetic. </member> <member name="display/window/stretch/scale" type="float" setter="" getter="" default="1.0"> + The scale factor multiplier to use for 2D elements. This multiplies the final scale factor determined by [member display/window/stretch/mode]. If using the [b]Disabled[/b] stretch mode, this scale factor is applied as-is. This can be adjusted to make the UI easier to read on certain displays. + </member> + <member name="display/window/stretch/scale_mode" type="String" setter="" getter="" default=""fractional""> + The policy to use to determine the final scale factor for 2D elements. This affects how [member display/window/stretch/scale] is applied, in addition to the automatic scale factor determined by [member display/window/stretch/mode]. </member> <member name="display/window/subwindows/embed_subwindows" type="bool" setter="" getter="" default="true"> If [code]true[/code] subwindows are embedded in the main window. @@ -2710,4 +2714,11 @@ If [code]true[/code], Godot will compile shaders required for XR. </member> </members> + <signals> + <signal name="settings_changed"> + <description> + Emitted when any setting is changed, up to once per process frame. + </description> + </signal> + </signals> </class> diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index 82498b9ba4..92cd11d720 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -569,6 +569,9 @@ <member name="content_scale_size" type="Vector2i" setter="set_content_scale_size" getter="get_content_scale_size" default="Vector2i(0, 0)"> Base size of the content (i.e. nodes that are drawn inside the window). If non-zero, [Window]'s content will be scaled when the window is resized to a different size. </member> + <member name="content_scale_stretch" type="int" setter="set_content_scale_stretch" getter="get_content_scale_stretch" enum="Window.ContentScaleStretch" default="0"> + The policy to use to determine the final scale factor for 2D elements. This affects how [member content_scale_factor] is applied, in addition to the automatic scale factor determined by [member content_scale_size]. + </member> <member name="current_screen" type="int" setter="set_current_screen" getter="get_current_screen"> The screen the window is currently on. </member> @@ -840,6 +843,12 @@ <constant name="CONTENT_SCALE_ASPECT_EXPAND" value="4" enum="ContentScaleAspect"> The content's aspect will be preserved. If the target size has different aspect from the base one, the content will stay in the top-left corner and add an extra visible area in the stretched space. </constant> + <constant name="CONTENT_SCALE_STRETCH_FRACTIONAL" value="0" enum="ContentScaleStretch"> + The content will be stretched according to a fractional factor. This fills all the space available in the window, but allows "pixel wobble" to occur due to uneven pixel scaling. + </constant> + <constant name="CONTENT_SCALE_STRETCH_INTEGER" value="1" enum="ContentScaleStretch"> + The content will be stretched only according to an integer factor, preserving sharp pixels. This may leave a black background visible on the window's edges depending on the window size. + </constant> <constant name="LAYOUT_DIRECTION_INHERITED" value="0" enum="LayoutDirection"> Automatic layout direction, determined from the parent window layout direction. </constant> diff --git a/editor/editor_log.cpp b/editor/editor_log.cpp index 1bc9f00f08..4c2f6b3176 100644 --- a/editor/editor_log.cpp +++ b/editor/editor_log.cpp @@ -212,7 +212,7 @@ void EditorLog::clear() { _clear_request(); } -void EditorLog::_process_message(const String &p_msg, MessageType p_type) { +void EditorLog::_process_message(const String &p_msg, MessageType p_type, bool p_clear) { if (messages.size() > 0 && messages[messages.size() - 1].text == p_msg && messages[messages.size() - 1].type == p_type) { // If previous message is the same as the new one, increase previous count rather than adding another // instance to the messages list. @@ -222,7 +222,7 @@ void EditorLog::_process_message(const String &p_msg, MessageType p_type) { _add_log_line(previous, collapse); } else { // Different message to the previous one received. - LogMessage message(p_msg, p_type); + LogMessage message(p_msg, p_type, p_clear); _add_log_line(message); messages.push_back(message); } @@ -237,9 +237,10 @@ void EditorLog::add_message(const String &p_msg, MessageType p_type) { // search functionality (see the comments on the PR above for more details). This behavior // also matches that of other IDE's. Vector<String> lines = p_msg.split("\n", true); + int line_count = lines.size(); - for (int i = 0; i < lines.size(); i++) { - _process_message(lines[i], p_type); + for (int i = 0; i < line_count; i++) { + _process_message(lines[i], p_type, i == line_count - 1); } } @@ -338,7 +339,9 @@ void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) { } else { log->add_text(p_message.text); } - log->pop_all(); // Pop all unclosed tags. + if (p_message.clear || p_message.type != MSG_TYPE_STD_RICH) { + log->pop_all(); // Pop all unclosed tags. + } log->add_newline(); if (p_replace_previous) { diff --git a/editor/editor_log.h b/editor/editor_log.h index b875066afa..07f3a25c3e 100644 --- a/editor/editor_log.h +++ b/editor/editor_log.h @@ -60,12 +60,14 @@ private: String text; MessageType type; int count = 1; + bool clear = true; LogMessage() {} - LogMessage(const String p_text, MessageType p_type) : + LogMessage(const String p_text, MessageType p_type, bool p_clear) : text(p_text), - type(p_type) { + type(p_type), + clear(p_clear) { } }; @@ -166,7 +168,7 @@ private: void _set_search_visible(bool p_visible); void _search_changed(const String &p_text); - void _process_message(const String &p_msg, MessageType p_type); + void _process_message(const String &p_msg, MessageType p_type, bool p_clear); void _reset_message_counts(); void _set_collapse(bool p_collapse); diff --git a/editor/plugin_config_dialog.cpp b/editor/plugin_config_dialog.cpp index 97398b8b69..7d7b156bd0 100644 --- a/editor/plugin_config_dialog.cpp +++ b/editor/plugin_config_dialog.cpp @@ -35,6 +35,7 @@ #include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/editor_scale.h" +#include "editor/gui/editor_validation_panel.h" #include "editor/project_settings_editor.h" #include "scene/gui/grid_container.h" @@ -96,52 +97,28 @@ void PluginConfigDialog::_on_canceled() { _clear_fields(); } -void PluginConfigDialog::_on_language_changed(const int) { - _on_required_text_changed(String()); -} - -void PluginConfigDialog::_on_required_text_changed(const String &) { +void PluginConfigDialog::_on_required_text_changed() { int lang_idx = script_option_edit->get_selected(); String ext = ScriptServer::get_language(lang_idx)->get_extension(); - Ref<Texture2D> valid_icon = get_theme_icon(SNAME("StatusSuccess"), SNAME("EditorIcons")); - Ref<Texture2D> invalid_icon = get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); - - // Set variables to assume all is valid - bool is_valid = true; - name_validation->set_texture(valid_icon); - subfolder_validation->set_texture(valid_icon); - script_validation->set_texture(valid_icon); - name_validation->set_tooltip_text(""); - subfolder_validation->set_tooltip_text(""); - script_validation->set_tooltip_text(""); - - // Change valid status to invalid depending on conditions. - Vector<String> errors; if (name_edit->get_text().is_empty()) { - is_valid = false; - name_validation->set_texture(invalid_icon); - name_validation->set_tooltip_text(TTR("Plugin name cannot be blank.")); + validation_panel->set_message(MSG_ID_PLUGIN, TTR("Plugin name cannot be blank."), EditorValidationPanel::MSG_ERROR); } if ((!script_edit->get_text().get_extension().is_empty() && script_edit->get_text().get_extension() != ext) || script_edit->get_text().ends_with(".")) { - is_valid = false; - script_validation->set_texture(invalid_icon); - script_validation->set_tooltip_text(vformat(TTR("Script extension must match chosen language extension (.%s)."), ext)); + validation_panel->set_message(MSG_ID_SCRIPT, vformat(TTR("Script extension must match chosen language extension (.%s)."), ext), EditorValidationPanel::MSG_ERROR); } - if (!subfolder_edit->get_text().is_empty() && !subfolder_edit->get_text().is_valid_filename()) { - is_valid = false; - subfolder_validation->set_texture(invalid_icon); - subfolder_validation->set_tooltip_text(TTR("Subfolder name is not a valid folder name.")); - } else { - String path = "res://addons/" + _get_subfolder(); - if (!_edit_mode && DirAccess::exists(path)) { // Only show this error if in "create" mode. - is_valid = false; - subfolder_validation->set_texture(invalid_icon); - subfolder_validation->set_tooltip_text(TTR("Subfolder cannot be one which already exists.")); + if (subfolder_edit->is_visible()) { + if (!subfolder_edit->get_text().is_empty() && !subfolder_edit->get_text().is_valid_filename()) { + validation_panel->set_message(MSG_ID_SUBFOLDER, TTR("Subfolder name is not a valid folder name."), EditorValidationPanel::MSG_ERROR); + } else { + String path = "res://addons/" + _get_subfolder(); + if (!_edit_mode && DirAccess::exists(path)) { // Only show this error if in "create" mode. + validation_panel->set_message(MSG_ID_SUBFOLDER, TTR("Subfolder cannot be one which already exists."), EditorValidationPanel::MSG_ERROR); + } } + } else { + validation_panel->set_message(MSG_ID_SUBFOLDER, "", EditorValidationPanel::MSG_OK); } - - get_ok_button()->set_disabled(!is_valid); } String PluginConfigDialog::_get_subfolder() { @@ -182,23 +159,20 @@ void PluginConfigDialog::config(const String &p_config_path) { _edit_mode = true; active_edit->hide(); - Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 2))->hide(); + Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 1))->hide(); subfolder_edit->hide(); - subfolder_validation->hide(); - Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 2))->hide(); + Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 1))->hide(); set_title(TTR("Edit a Plugin")); } else { _clear_fields(); _edit_mode = false; active_edit->show(); - Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 2))->show(); + Object::cast_to<Label>(active_edit->get_parent()->get_child(active_edit->get_index() - 1))->show(); subfolder_edit->show(); - subfolder_validation->show(); - Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 2))->show(); + Object::cast_to<Label>(subfolder_edit->get_parent()->get_child(subfolder_edit->get_index() - 1))->show(); set_title(TTR("Create a Plugin")); } - // Simulate text changing so the errors populate. - _on_required_text_changed(""); + validation_panel->update(); get_ok_button()->set_disabled(!_edit_mode); set_ok_button_text(_edit_mode ? TTR("Update") : TTR("Create")); @@ -218,7 +192,7 @@ PluginConfigDialog::PluginConfigDialog() { add_child(vbox); GridContainer *grid = memnew(GridContainer); - grid->set_columns(3); + grid->set_columns(2); grid->set_v_size_flags(Control::SIZE_EXPAND_FILL); vbox->add_child(grid); @@ -228,12 +202,7 @@ PluginConfigDialog::PluginConfigDialog() { name_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(name_lb); - name_validation = memnew(TextureRect); - name_validation->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - grid->add_child(name_validation); - name_edit = memnew(LineEdit); - name_edit->connect("text_changed", callable_mp(this, &PluginConfigDialog::_on_required_text_changed)); name_edit->set_placeholder("MyPlugin"); name_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); grid->add_child(name_edit); @@ -244,14 +213,9 @@ PluginConfigDialog::PluginConfigDialog() { subfolder_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(subfolder_lb); - subfolder_validation = memnew(TextureRect); - subfolder_validation->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - grid->add_child(subfolder_validation); - subfolder_edit = memnew(LineEdit); subfolder_edit->set_placeholder("\"my_plugin\" -> res://addons/my_plugin"); subfolder_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); - subfolder_edit->connect("text_changed", callable_mp(this, &PluginConfigDialog::_on_required_text_changed)); grid->add_child(subfolder_edit); // Description @@ -260,9 +224,6 @@ PluginConfigDialog::PluginConfigDialog() { desc_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(desc_lb); - Control *desc_spacer = memnew(Control); - grid->add_child(desc_spacer); - desc_edit = memnew(TextEdit); desc_edit->set_custom_minimum_size(Size2(400, 80) * EDSCALE); desc_edit->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); @@ -276,9 +237,6 @@ PluginConfigDialog::PluginConfigDialog() { author_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(author_lb); - Control *author_spacer = memnew(Control); - grid->add_child(author_spacer); - author_edit = memnew(LineEdit); author_edit->set_placeholder("Godette"); author_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -290,9 +248,6 @@ PluginConfigDialog::PluginConfigDialog() { version_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(version_lb); - Control *version_spacer = memnew(Control); - grid->add_child(version_spacer); - version_edit = memnew(LineEdit); version_edit->set_placeholder("1.0"); version_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -304,9 +259,6 @@ PluginConfigDialog::PluginConfigDialog() { script_option_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(script_option_lb); - Control *script_opt_spacer = memnew(Control); - grid->add_child(script_opt_spacer); - script_option_edit = memnew(OptionButton); int default_lang = 0; for (int i = 0; i < ScriptServer::get_language_count(); i++) { @@ -318,7 +270,6 @@ PluginConfigDialog::PluginConfigDialog() { } script_option_edit->select(default_lang); grid->add_child(script_option_edit); - script_option_edit->connect("item_selected", callable_mp(this, &PluginConfigDialog::_on_language_changed)); // Plugin Script Name Label *script_lb = memnew(Label); @@ -326,12 +277,7 @@ PluginConfigDialog::PluginConfigDialog() { script_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(script_lb); - script_validation = memnew(TextureRect); - script_validation->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - grid->add_child(script_validation); - script_edit = memnew(LineEdit); - script_edit->connect("text_changed", callable_mp(this, &PluginConfigDialog::_on_required_text_changed)); script_edit->set_placeholder("\"plugin.gd\" -> res://addons/my_plugin/plugin.gd"); script_edit->set_h_size_flags(Control::SIZE_EXPAND_FILL); grid->add_child(script_edit); @@ -343,12 +289,26 @@ PluginConfigDialog::PluginConfigDialog() { active_lb->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); grid->add_child(active_lb); - Control *active_spacer = memnew(Control); - grid->add_child(active_spacer); - active_edit = memnew(CheckBox); active_edit->set_pressed(true); grid->add_child(active_edit); + + Control *spacing = memnew(Control); + vbox->add_child(spacing); + spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); + + validation_panel = memnew(EditorValidationPanel); + vbox->add_child(validation_panel); + validation_panel->add_line(MSG_ID_PLUGIN, TTR("Plugin name is valid.")); + validation_panel->add_line(MSG_ID_SCRIPT, TTR("Script extension is valid.")); + validation_panel->add_line(MSG_ID_SUBFOLDER, TTR("Subfolder name is valid.")); + validation_panel->set_update_callback(callable_mp(this, &PluginConfigDialog::_on_required_text_changed)); + validation_panel->set_accept_button(get_ok_button()); + + script_option_edit->connect("item_selected", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); + name_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); + subfolder_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); + script_edit->connect("text_changed", callable_mp(validation_panel, &EditorValidationPanel::update).unbind(1)); } PluginConfigDialog::~PluginConfigDialog() { diff --git a/editor/plugin_config_dialog.h b/editor/plugin_config_dialog.h index 50b901a39e..1221d347a7 100644 --- a/editor/plugin_config_dialog.h +++ b/editor/plugin_config_dialog.h @@ -35,12 +35,21 @@ #include "scene/gui/dialogs.h" #include "scene/gui/line_edit.h" #include "scene/gui/option_button.h" +#include "scene/gui/panel_container.h" #include "scene/gui/text_edit.h" #include "scene/gui/texture_rect.h" +class EditorValidationPanel; + class PluginConfigDialog : public ConfirmationDialog { GDCLASS(PluginConfigDialog, ConfirmationDialog); + enum { + MSG_ID_PLUGIN, + MSG_ID_SUBFOLDER, + MSG_ID_SCRIPT, + }; + LineEdit *name_edit = nullptr; LineEdit *subfolder_edit = nullptr; TextEdit *desc_edit = nullptr; @@ -50,17 +59,14 @@ class PluginConfigDialog : public ConfirmationDialog { LineEdit *script_edit = nullptr; CheckBox *active_edit = nullptr; - TextureRect *name_validation = nullptr; - TextureRect *subfolder_validation = nullptr; - TextureRect *script_validation = nullptr; + EditorValidationPanel *validation_panel = nullptr; bool _edit_mode = false; void _clear_fields(); void _on_confirmed(); void _on_canceled(); - void _on_language_changed(const int p_language); - void _on_required_text_changed(const String &p_text); + void _on_required_text_changed(); String _get_subfolder(); static String _to_absolute_plugin_path(const String &p_plugin_name); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index d05db7aa63..65563bd1a3 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1675,6 +1675,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (_edit.mode != TRANSFORM_NONE && b->is_pressed()) { cancel_transform(); + break; } if (b->is_pressed()) { @@ -2007,7 +2008,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { _edit.mode = TRANSFORM_TRANSLATE; } - if (_edit.mode == TRANSFORM_NONE) { + if (_edit.mode == TRANSFORM_NONE || _edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) { return; } @@ -2145,6 +2146,43 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; } + if (_edit.instant) { + // In a Blender-style transform, numbers set the magnitude of the transform. + // E.g. pressing g4.5x means "translate 4.5 units along the X axis". + // Use the Unicode value because we care about the text, not the actual keycode. + // This ensures numbers work consistently across different keyboard language layouts. + bool processed = true; + Key key = k->get_physical_keycode(); + char32_t unicode = k->get_unicode(); + if (unicode >= '0' && unicode <= '9') { + uint32_t value = uint32_t(unicode - Key::KEY_0); + if (_edit.numeric_next_decimal < 0) { + _edit.numeric_input = _edit.numeric_input + value * Math::pow(10.0, _edit.numeric_next_decimal--); + } else { + _edit.numeric_input = _edit.numeric_input * 10 + value; + } + update_transform_numeric(); + } else if (unicode == '-') { + _edit.numeric_negate = !_edit.numeric_negate; + update_transform_numeric(); + } else if (unicode == '.') { + if (_edit.numeric_next_decimal == 0) { + _edit.numeric_next_decimal = -1; + } + } else if (key == Key::ENTER || key == Key::KP_ENTER || key == Key::SPACE) { + commit_transform(); + } else { + processed = false; + } + + if (processed) { + // Ignore mouse inputs once we receive a numeric input. + set_process_input(false); + accept_event(); + return; + } + } + if (EDITOR_GET("editors/3d/navigation/emulate_numpad")) { const Key code = k->get_physical_keycode(); if (code >= Key::KEY_0 && code <= Key::KEY_9) { @@ -2165,26 +2203,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } else { // We're actively transforming, handle keys specially TransformPlane new_plane = TRANSFORM_VIEW; - String new_message; if (ED_IS_SHORTCUT("spatial_editor/lock_transform_x", p_event)) { new_plane = TRANSFORM_X_AXIS; - new_message = TTR("X-Axis Transform."); } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_y", p_event)) { new_plane = TRANSFORM_Y_AXIS; - new_message = TTR("Y-Axis Transform."); } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_z", p_event)) { new_plane = TRANSFORM_Z_AXIS; - new_message = TTR("Z-Axis Transform."); } else if (_edit.mode != TRANSFORM_ROTATE) { // rotating on a plane doesn't make sense if (ED_IS_SHORTCUT("spatial_editor/lock_transform_yz", p_event)) { new_plane = TRANSFORM_YZ; - new_message = TTR("YZ-Plane Transform."); } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xz", p_event)) { new_plane = TRANSFORM_XZ; - new_message = TTR("XZ-Plane Transform."); } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xy", p_event)) { new_plane = TRANSFORM_XY; - new_message = TTR("XY-Plane Transform."); } } @@ -2201,8 +2232,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { _edit.plane = TRANSFORM_VIEW; spatial_editor->set_local_coords_enabled(false); } - update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT)); - set_message(new_message, 2); + if (_edit.numeric_input != 0 || _edit.numeric_next_decimal != 0) { + update_transform_numeric(); + } else { + update_transform(Input::get_singleton()->is_key_pressed(Key::SHIFT)); + } accept_event(); return; } @@ -4575,6 +4609,43 @@ void Node3DEditorViewport::commit_transform() { set_message(""); } +void Node3DEditorViewport::apply_transform(Vector3 p_motion, double p_snap) { + bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); + List<Node *> &selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + if (sp->has_meta("_edit_lock_")) { + continue; + } + + if (se->gizmo.is_valid()) { + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; + Transform3D new_xform = _compute_transform(_edit.mode, se->original * xform, xform, p_motion, p_snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo. + if (!local_coords) { + new_xform = se->original.affine_inverse() * new_xform; + } + se->gizmo->set_subgizmo_transform(GE.key, new_xform); + } + } else { + Transform3D new_xform = _compute_transform(_edit.mode, se->original, se->original_local, p_motion, p_snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW); + _transform_gizmo_apply(se->sp, new_xform, local_coords); + } + } + + spatial_editor->update_transform_gizmo(); + surface->queue_redraw(); +} + // Update the current transform operation in response to an input. void Node3DEditorViewport::update_transform(bool p_shift) { Vector3 ray_pos = _get_ray_pos(_edit.mouse_pos); @@ -4670,43 +4741,11 @@ void Node3DEditorViewport::update_transform(bool p_shift) { set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " + String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); if (local_coords) { + // TODO: needed? motion = _edit.original.basis.inverse().xform(motion); } - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - if (se->gizmo.is_valid()) { - for (KeyValue<int, Transform3D> &GE : se->subgizmos) { - Transform3D xform = GE.value; - Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords, _edit.plane != TRANSFORM_VIEW); // Force orthogonal with subgizmo. - if (!local_coords) { - new_xform = se->original.affine_inverse() * new_xform; - } - se->gizmo->set_subgizmo_transform(GE.key, new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW); - _transform_gizmo_apply(se->sp, new_xform, local_coords); - } - } - - spatial_editor->update_transform_gizmo(); - surface->queue_redraw(); - + apply_transform(motion, snap); } break; case TRANSFORM_TRANSLATE: { @@ -4776,38 +4815,7 @@ void Node3DEditorViewport::update_transform(bool p_shift) { motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion); } - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - if (se->gizmo.is_valid()) { - for (KeyValue<int, Transform3D> &GE : se->subgizmos) { - Transform3D xform = GE.value; - Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords, true); // Force orthogonal with subgizmo. - new_xform = se->original.affine_inverse() * new_xform; - se->gizmo->set_subgizmo_transform(GE.key, new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS); - _transform_gizmo_apply(se->sp, new_xform, false); - } - } - - spatial_editor->update_transform_gizmo(); - surface->queue_redraw(); - + apply_transform(motion, snap); } break; case TRANSFORM_ROTATE: { @@ -4876,53 +4884,85 @@ void Node3DEditorViewport::update_transform(bool p_shift) { bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - Vector3 compute_axis = local_coords ? local_axis : global_axis; - if (se->gizmo.is_valid()) { - for (KeyValue<int, Transform3D> &GE : se->subgizmos) { - Transform3D xform = GE.value; - - Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords, true); // Force orthogonal with subgizmo. - if (!local_coords) { - new_xform = se->original.affine_inverse() * new_xform; - } - se->gizmo->set_subgizmo_transform(GE.key, new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS); - _transform_gizmo_apply(se->sp, new_xform, local_coords); - } - } - - spatial_editor->update_transform_gizmo(); - surface->queue_redraw(); - + Vector3 compute_axis = local_coords ? local_axis : global_axis; + apply_transform(compute_axis, angle); } break; default: { } } } -// Perform cleanup after a transform operation is committed or canceled. +void Node3DEditorViewport::update_transform_numeric() { + Vector3 motion; + switch (_edit.plane) { + case TRANSFORM_VIEW: { + switch (_edit.mode) { + case TRANSFORM_TRANSLATE: + motion = Vector3(1, 0, 0); + break; + case TRANSFORM_ROTATE: + motion = spatial_editor->get_gizmo_transform().basis.xform_inv(_get_camera_normal()).normalized(); + break; + case TRANSFORM_SCALE: + motion = Vector3(1, 1, 1); + break; + case TRANSFORM_NONE: + ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric."); + } + break; + } + case TRANSFORM_X_AXIS: + motion = Vector3(1, 0, 0); + break; + case TRANSFORM_Y_AXIS: + motion = Vector3(0, 1, 0); + break; + case TRANSFORM_Z_AXIS: + motion = Vector3(0, 0, 1); + break; + case TRANSFORM_XY: + motion = Vector3(1, 1, 0); + break; + case TRANSFORM_XZ: + motion = Vector3(1, 0, 1); + break; + case TRANSFORM_YZ: + motion = Vector3(0, 1, 1); + break; + } + + double value = _edit.numeric_input * (_edit.numeric_negate ? -1 : 1); + double extra = 0.0; + switch (_edit.mode) { + case TRANSFORM_TRANSLATE: + motion *= value; + set_message(vformat(TTR("Translating %s."), motion)); + break; + case TRANSFORM_ROTATE: + extra = Math::deg_to_rad(value); + set_message(vformat(TTR("Rotating %f degrees."), value)); + break; + case TRANSFORM_SCALE: + // To halve the size of an object in Blender, you scale it by 0.5. + // Doing the same in Godot is considered scaling it by -0.5. + motion *= (value - 1.0); + set_message(vformat(TTR("Scaling %s."), motion)); + break; + case TRANSFORM_NONE: + ERR_FAIL_MSG("_edit.mode cannot be TRANSFORM_NONE in update_transform_numeric."); + } + + apply_transform(motion, extra); +} + +// Perform cleanup after a transform operation is committed or cancelled. void Node3DEditorViewport::finish_transform() { - spatial_editor->set_local_coords_enabled(_edit.original_local); _edit.mode = TRANSFORM_NONE; _edit.instant = false; + _edit.numeric_input = 0; + _edit.numeric_next_decimal = 0; + _edit.numeric_negate = false; + spatial_editor->set_local_coords_enabled(_edit.original_local); spatial_editor->update_transform_gizmo(); surface->queue_redraw(); set_process_input(false); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 79674bdd64..e58e224ff4 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -349,6 +349,15 @@ private: Variant gizmo_initial_value; bool original_local; bool instant; + + // Numeric blender-style transforms (e.g. 'g5x'). + // numeric_input tracks the current input value, e.g. 1.23. + // numeric_negate indicates whether '-' has been pressed to negate the value + // while numeric_next_decimal is 0, numbers are input before the decimal point + // after pressing '.', numeric next decimal changes to -1, and decrements after each press. + double numeric_input = 0.0; + bool numeric_negate = false; + int numeric_next_decimal = 0; } _edit; struct Cursor { @@ -445,7 +454,9 @@ private: void begin_transform(TransformMode p_mode, bool instant); void commit_transform(); + void apply_transform(Vector3 p_motion, double p_snap); void update_transform(bool p_shift); + void update_transform_numeric(); void finish_transform(); void register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode, bool p_physical = false); diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index fbb724906b..849a6cc097 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -275,6 +275,7 @@ void register_editor_types() { GLOBAL_DEF("editor/version_control/autoload_on_startup", false); EditorInterface::create(); + Engine::get_singleton()->add_singleton(Engine::Singleton("EditorInterface", EditorInterface::get_singleton())); OS::get_singleton()->benchmark_end_measure("register_editor_types"); } diff --git a/main/main.cpp b/main/main.cpp index 4bb5e7bf13..220afda5de 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -3122,6 +3122,7 @@ bool Main::start() { Size2i stretch_size = Size2i(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height")); real_t stretch_scale = GLOBAL_GET("display/window/stretch/scale"); + String stretch_scale_mode = GLOBAL_GET("display/window/stretch/scale_mode"); Window::ContentScaleMode cs_sm = Window::CONTENT_SCALE_MODE_DISABLED; if (stretch_mode == "canvas_items") { @@ -3141,8 +3142,14 @@ bool Main::start() { cs_aspect = Window::CONTENT_SCALE_ASPECT_EXPAND; } + Window::ContentScaleStretch cs_stretch = Window::CONTENT_SCALE_STRETCH_FRACTIONAL; + if (stretch_scale_mode == "integer") { + cs_stretch = Window::CONTENT_SCALE_STRETCH_INTEGER; + } + sml->get_root()->set_content_scale_mode(cs_sm); sml->get_root()->set_content_scale_aspect(cs_aspect); + sml->get_root()->set_content_scale_stretch(cs_stretch); sml->get_root()->set_content_scale_size(stretch_size); sml->get_root()->set_content_scale_factor(stretch_scale); diff --git a/misc/scripts/validate_extension_api.sh b/misc/scripts/validate_extension_api.sh index e06d52115a..f2f7c28e70 100755 --- a/misc/scripts/validate_extension_api.sh +++ b/misc/scripts/validate_extension_api.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -uo pipefail +set -o pipefail if [ ! -f "version.py" ]; then echo "Warning: This script is intended to be run from the root of the Godot repository." @@ -18,7 +18,7 @@ make_annotation() local body=$2 local type=$3 local file=$4 - if [ ! -v GITHUB_OUTPUT ]; then + if [[ "$GITHUB_OUTPUT" == "" ]]; then echo "$title" echo "$body" else @@ -43,8 +43,8 @@ while read -r file; do awk '/^Validate extension JSON:/' - < "$file" | sort > "$allowed_errors" # Differences between the expected and actual errors - new_validation_error="$(comm "$validation_output" "$allowed_errors" -23)" - obsolete_validation_error="$(comm "$validation_output" "$allowed_errors" -13)" + new_validation_error="$(comm -23 "$validation_output" "$allowed_errors")" + obsolete_validation_error="$(comm -13 "$validation_output" "$allowed_errors")" if [ -n "$obsolete_validation_error" ]; then make_annotation "The following validation errors no longer occur (compared to $reference_tag):" "$obsolete_validation_error" warning "$file" diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index d7d484d166..8a292fd73a 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -23,7 +23,7 @@ namespace GodotTools.Build if (dotnetPath == null) throw new FileNotFoundException("Cannot find the dotnet executable."); - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); var startInfo = new ProcessStartInfo(dotnetPath); @@ -94,7 +94,7 @@ namespace GodotTools.Build if (dotnetPath == null) throw new FileNotFoundException("Cannot find the dotnet executable."); - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); var startInfo = new ProcessStartInfo(dotnetPath); diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index 618d255938..ec28658557 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -473,10 +473,9 @@ namespace GodotTools } } - var editorInterface = GetEditorInterface(); - var editorBaseControl = editorInterface.GetBaseControl(); + var editorBaseControl = EditorInterface.Singleton.GetBaseControl(); - _editorSettings = editorInterface.GetEditorSettings(); + _editorSettings = EditorInterface.Singleton.GetEditorSettings(); _errorDialog = new AcceptDialog(); editorBaseControl.AddChild(_errorDialog); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 5d1a2277f9..65b77112aa 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -79,7 +79,7 @@ namespace GodotTools.Ides public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000) { - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); var editorId = editorSettings.GetSetting(GodotSharpEditor.Settings.ExternalEditor).As<ExternalEditorId>(); string editorIdentity = GetExternalEditorIdentity(editorId); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index 0d77b8999a..fbbd01dafd 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -23,7 +23,7 @@ namespace GodotTools.Ides.Rider private static string GetRiderPathFromSettings() { - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); if (editorSettings.HasSetting(EditorPathSettingName)) return (string)editorSettings.GetSetting(EditorPathSettingName); return null; @@ -31,7 +31,7 @@ namespace GodotTools.Ides.Rider public static void Initialize() { - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); var editor = editorSettings.GetSetting(GodotSharpEditor.Settings.ExternalEditor).As<ExternalEditorId>(); if (editor == ExternalEditorId.Rider) { @@ -92,7 +92,7 @@ namespace GodotTools.Ides.Rider string newPath = riderInfos.Length > 0 ? riderInfos[riderInfos.Length - 1].Path : allInfos[allInfos.Length - 1].Path; - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + var editorSettings = EditorInterface.Singleton.GetEditorSettings(); editorSettings.SetSetting(EditorPathSettingName, newPath); Globals.EditorDef(EditorPathSettingName, newPath); return newPath; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 1554b89c33..342c94cbd9 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -4014,7 +4014,7 @@ void BindingsGenerator::_initialize_blacklisted_methods() { } void BindingsGenerator::_initialize_compat_singletons() { - // No compat singletons yet. + compat_singletons.insert("EditorInterface"); } void BindingsGenerator::_log(const char *p_format, ...) { diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 68241337c9..443a639ff2 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -3128,6 +3128,7 @@ void CodeEdit::_filter_code_completion_candidates_impl() { } String target_lower = option.display.to_lower(); + int long_option = target_lower.size() > 50; const char32_t *string_to_complete_char_lower = &string_to_complete_lower[0]; const char32_t *target_char_lower = &target_lower[0]; @@ -3142,27 +3143,34 @@ void CodeEdit::_filter_code_completion_candidates_impl() { for (int i = 1; *string_to_complete_char_lower && (all_possible_subsequence_matches.size() > 0); i++, string_to_complete_char_lower++) { // find all occurrences of ssq_lower to avoid looking everywhere each time Vector<int> all_ocurence; - for (int j = i; j < target_lower.length(); j++) { - if (target_lower[j] == *string_to_complete_char_lower) { - all_ocurence.push_back(j); + if (long_option) { + all_ocurence.push_back(target_lower.find_char(*string_to_complete_char_lower)); + } else { + for (int j = i; j < target_lower.length(); j++) { + if (target_lower[j] == *string_to_complete_char_lower) { + all_ocurence.push_back(j); + } } } Vector<Vector<Pair<int, int>>> next_subsequence_matches; - for (Vector<Pair<int, int>> &subsequence_matches : all_possible_subsequence_matches) { - Pair<int, int> match_last_segment = subsequence_matches[subsequence_matches.size() - 1]; + for (Vector<Pair<int, int>> &subsequence_match : all_possible_subsequence_matches) { + Pair<int, int> match_last_segment = subsequence_match[subsequence_match.size() - 1]; int next_index = match_last_segment.first + match_last_segment.second; // get the last index from current sequence // and look for next char starting from that index if (target_lower[next_index] == *string_to_complete_char_lower) { - Vector<Pair<int, int>> new_matches = subsequence_matches; - new_matches.write[new_matches.size() - 1].second++; - next_subsequence_matches.push_back(new_matches); + Vector<Pair<int, int>> new_match = subsequence_match; + new_match.write[new_match.size() - 1].second++; + next_subsequence_matches.push_back(new_match); + if (long_option) { + continue; + } } for (int index : all_ocurence) { if (index > next_index) { - Vector<Pair<int, int>> new_matches = subsequence_matches; - new_matches.push_back({ index, 1 }); - next_subsequence_matches.push_back(new_matches); + Vector<Pair<int, int>> new_match = subsequence_match; + new_match.push_back({ index, 1 }); + next_subsequence_matches.push_back(new_match); } } } diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 875b53203a..0b95406b94 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -1001,6 +1001,17 @@ void Window::_update_viewport_size() { float font_oversampling = 1.0; window_transform = Transform2D(); + if (content_scale_stretch == Window::CONTENT_SCALE_STRETCH_INTEGER) { + // We always want to make sure that the content scale factor is a whole + // number, else there will be pixel wobble no matter what. + content_scale_factor = Math::floor(content_scale_factor); + + // A content scale factor of zero is pretty useless. + if (content_scale_factor < 1) { + content_scale_factor = 1; + } + } + if (content_scale_mode == CONTENT_SCALE_MODE_DISABLED || content_scale_size.x == 0 || content_scale_size.y == 0) { font_oversampling = content_scale_factor; final_size = size; @@ -1054,13 +1065,26 @@ void Window::_update_viewport_size() { screen_size = screen_size.floor(); viewport_size = viewport_size.floor(); + if (content_scale_stretch == Window::CONTENT_SCALE_STRETCH_INTEGER) { + Size2i screen_scale = (screen_size / viewport_size).floor(); + int scale_factor = MIN(screen_scale.x, screen_scale.y); + + if (scale_factor < 1) { + scale_factor = 1; + } + + screen_size = viewport_size * scale_factor; + } + Size2 margin; Size2 offset; - if (content_scale_aspect != CONTENT_SCALE_ASPECT_EXPAND && screen_size.x < video_mode.x) { + if (screen_size.x < video_mode.x) { margin.x = Math::round((video_mode.x - screen_size.x) / 2.0); offset.x = Math::round(margin.x * viewport_size.y / screen_size.y); - } else if (content_scale_aspect != CONTENT_SCALE_ASPECT_EXPAND && screen_size.y < video_mode.y) { + } + + if (screen_size.y < video_mode.y) { margin.y = Math::round((video_mode.y - screen_size.y) / 2.0); offset.y = Math::round(margin.y * viewport_size.x / screen_size.x); } @@ -1337,6 +1361,15 @@ Window::ContentScaleAspect Window::get_content_scale_aspect() const { return content_scale_aspect; } +void Window::set_content_scale_stretch(ContentScaleStretch p_stretch) { + content_scale_stretch = p_stretch; + _update_viewport_size(); +} + +Window::ContentScaleStretch Window::get_content_scale_stretch() const { + return content_scale_stretch; +} + void Window::set_content_scale_factor(real_t p_factor) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_COND(p_factor <= 0); @@ -2593,6 +2626,9 @@ void Window::_bind_methods() { ClassDB::bind_method(D_METHOD("set_content_scale_aspect", "aspect"), &Window::set_content_scale_aspect); ClassDB::bind_method(D_METHOD("get_content_scale_aspect"), &Window::get_content_scale_aspect); + ClassDB::bind_method(D_METHOD("set_content_scale_stretch", "stretch"), &Window::set_content_scale_stretch); + ClassDB::bind_method(D_METHOD("get_content_scale_stretch"), &Window::get_content_scale_stretch); + ClassDB::bind_method(D_METHOD("set_content_scale_factor", "factor"), &Window::set_content_scale_factor); ClassDB::bind_method(D_METHOD("get_content_scale_factor"), &Window::get_content_scale_factor); @@ -2711,7 +2747,8 @@ void Window::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "content_scale_size"), "set_content_scale_size", "get_content_scale_size"); ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_mode", PROPERTY_HINT_ENUM, "Disabled,Canvas Items,Viewport"), "set_content_scale_mode", "get_content_scale_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_aspect", PROPERTY_HINT_ENUM, "Ignore,Keep,Keep Width,Keep Height,Expand"), "set_content_scale_aspect", "get_content_scale_aspect"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "content_scale_factor"), "set_content_scale_factor", "get_content_scale_factor"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "content_scale_stretch", PROPERTY_HINT_ENUM, "Fractional,Integer"), "set_content_scale_stretch", "get_content_scale_stretch"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "content_scale_factor", PROPERTY_HINT_RANGE, "0.5,8.0,0.01"), "set_content_scale_factor", "get_content_scale_factor"); ADD_GROUP("Localization", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_translate"), "set_auto_translate", "is_auto_translating"); @@ -2763,6 +2800,9 @@ void Window::_bind_methods() { BIND_ENUM_CONSTANT(CONTENT_SCALE_ASPECT_KEEP_HEIGHT); BIND_ENUM_CONSTANT(CONTENT_SCALE_ASPECT_EXPAND); + BIND_ENUM_CONSTANT(CONTENT_SCALE_STRETCH_FRACTIONAL); + BIND_ENUM_CONSTANT(CONTENT_SCALE_STRETCH_INTEGER); + BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_INHERITED); BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LOCALE); BIND_ENUM_CONSTANT(LAYOUT_DIRECTION_LTR); diff --git a/scene/main/window.h b/scene/main/window.h index 18ddd89662..d5b8cd4ead 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -78,6 +78,11 @@ public: CONTENT_SCALE_ASPECT_EXPAND, }; + enum ContentScaleStretch { + CONTENT_SCALE_STRETCH_FRACTIONAL, + CONTENT_SCALE_STRETCH_INTEGER, + }; + enum LayoutDirection { LAYOUT_DIRECTION_INHERITED, LAYOUT_DIRECTION_LOCALE, @@ -135,6 +140,7 @@ private: Size2i content_scale_size; ContentScaleMode content_scale_mode = CONTENT_SCALE_MODE_DISABLED; ContentScaleAspect content_scale_aspect = CONTENT_SCALE_ASPECT_IGNORE; + ContentScaleStretch content_scale_stretch = CONTENT_SCALE_STRETCH_FRACTIONAL; real_t content_scale_factor = 1.0; void _make_window(); @@ -299,6 +305,9 @@ public: void set_content_scale_aspect(ContentScaleAspect p_aspect); ContentScaleAspect get_content_scale_aspect() const; + void set_content_scale_stretch(ContentScaleStretch p_stretch); + ContentScaleStretch get_content_scale_stretch() const; + void set_content_scale_factor(real_t p_factor); real_t get_content_scale_factor() const; @@ -420,6 +429,7 @@ VARIANT_ENUM_CAST(Window::Mode); VARIANT_ENUM_CAST(Window::Flags); VARIANT_ENUM_CAST(Window::ContentScaleMode); VARIANT_ENUM_CAST(Window::ContentScaleAspect); +VARIANT_ENUM_CAST(Window::ContentScaleStretch); VARIANT_ENUM_CAST(Window::LayoutDirection); VARIANT_ENUM_CAST(Window::WindowInitialPosition); diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index 9c7990d679..2974e9c4a3 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1010,13 +1010,6 @@ void RenderForwardClustered::_fill_render_list(RenderListType p_render_list, con if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_USES_DEPTH_TEXTURE) { scene_state.used_depth_texture = true; } - - if (p_color_pass_flags & COLOR_PASS_FLAG_MOTION_VECTORS) { - if ((flags & (INSTANCE_DATA_FLAG_MULTIMESH | INSTANCE_DATA_FLAG_PARTICLES)) == INSTANCE_DATA_FLAG_MULTIMESH && RendererRD::MeshStorage::get_singleton()->_multimesh_enable_motion_vectors(inst->data->base)) { - inst->transforms_uniform_set = mesh_storage->multimesh_get_3d_uniform_set(inst->data->base, scene_shader.default_shader_rd, TRANSFORMS_UNIFORM_SET); - } - } - } else if (p_pass_mode == PASS_MODE_SHADOW || p_pass_mode == PASS_MODE_SHADOW_DP) { if (surf->flags & GeometryInstanceSurfaceDataCache::FLAG_PASS_SHADOW) { rl->add_element(surf); diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 67b5cdd291..4bfec8ae8d 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -1289,12 +1289,9 @@ void MeshStorage::multimesh_allocate_data(RID p_multimesh, int p_instances, RS:: multimesh->dependency.changed_notify(Dependency::DEPENDENCY_CHANGED_MULTIMESH); } -bool MeshStorage::_multimesh_enable_motion_vectors(RID p_multimesh) { - MultiMesh *multimesh = multimesh_owner.get_or_null(p_multimesh); - ERR_FAIL_COND_V(!multimesh, false); - +void MeshStorage::_multimesh_enable_motion_vectors(MultiMesh *multimesh) { if (multimesh->motion_vectors_enabled) { - return false; + return; } multimesh->motion_vectors_enabled = true; @@ -1307,22 +1304,30 @@ bool MeshStorage::_multimesh_enable_motion_vectors(RID p_multimesh) { multimesh->data_cache.append_array(multimesh->data_cache); } - if (multimesh->buffer_set) { + uint32_t buffer_size = multimesh->instances * multimesh->stride_cache * sizeof(float); + uint32_t new_buffer_size = buffer_size * 2; + RID new_buffer = RD::get_singleton()->storage_buffer_create(new_buffer_size); + + if (multimesh->buffer_set && multimesh->data_cache.is_empty()) { + // If the buffer was set but there's no data cached in the CPU, we must download it from the GPU and + // upload it because RD does not provide a way to copy the buffer directly yet. RD::get_singleton()->barrier(); Vector<uint8_t> buffer_data = RD::get_singleton()->buffer_get_data(multimesh->buffer); - if (!multimesh->data_cache.is_empty()) { - memcpy(buffer_data.ptrw(), multimesh->data_cache.ptr(), buffer_data.size()); - } + ERR_FAIL_COND(buffer_data.size() != int(buffer_size)); + RD::get_singleton()->buffer_update(new_buffer, 0, buffer_size, buffer_data.ptr(), RD::BARRIER_MASK_NO_BARRIER); + RD::get_singleton()->buffer_update(new_buffer, buffer_size, buffer_size, buffer_data.ptr()); + } else if (!multimesh->data_cache.is_empty()) { + // Simply upload the data cached in the CPU, which should already be doubled in size. + ERR_FAIL_COND(multimesh->data_cache.size() != int(new_buffer_size)); + RD::get_singleton()->buffer_update(new_buffer, 0, new_buffer_size, multimesh->data_cache.ptr()); + } + if (multimesh->buffer.is_valid()) { RD::get_singleton()->free(multimesh->buffer); - uint32_t buffer_size = multimesh->instances * multimesh->stride_cache * sizeof(float) * 2; - multimesh->buffer = RD::get_singleton()->storage_buffer_create(buffer_size); - RD::get_singleton()->buffer_update(multimesh->buffer, 0, buffer_data.size(), buffer_data.ptr(), RD::BARRIER_MASK_NO_BARRIER); - RD::get_singleton()->buffer_update(multimesh->buffer, buffer_data.size(), buffer_data.size(), buffer_data.ptr()); - multimesh->uniform_set_3d = RID(); // Cleared by dependency - return true; } - return false; // Update the transforms uniform set cache + + multimesh->buffer = new_buffer; + multimesh->uniform_set_3d = RID(); // Cleared by dependency. } void MeshStorage::_multimesh_get_motion_vectors_offsets(RID p_multimesh, uint32_t &r_current_offset, uint32_t &r_prev_offset) { @@ -1531,6 +1536,12 @@ void MeshStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index, ERR_FAIL_COND(multimesh->xform_format != RS::MULTIMESH_TRANSFORM_3D); _multimesh_make_local(multimesh); + + bool uses_motion_vectors = (RSG::viewport->get_num_viewports_with_motion_vectors() > 0); + if (uses_motion_vectors) { + _multimesh_enable_motion_vectors(multimesh); + } + _multimesh_update_motion_vectors_data_cache(multimesh); { @@ -1749,6 +1760,11 @@ void MeshStorage::multimesh_set_buffer(RID p_multimesh, const Vector<float> &p_b ERR_FAIL_COND(!multimesh); ERR_FAIL_COND(p_buffer.size() != (multimesh->instances * (int)multimesh->stride_cache)); + bool uses_motion_vectors = (RSG::viewport->get_num_viewports_with_motion_vectors() > 0); + if (uses_motion_vectors) { + _multimesh_enable_motion_vectors(multimesh); + } + if (multimesh->motion_vectors_enabled) { uint32_t frame = RSG::rasterizer->get_frame_number(); diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h index 4d46a62a76..c23a5b1449 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h @@ -234,6 +234,7 @@ private: MultiMesh *multimesh_dirty_list = nullptr; _FORCE_INLINE_ void _multimesh_make_local(MultiMesh *multimesh) const; + _FORCE_INLINE_ void _multimesh_enable_motion_vectors(MultiMesh *multimesh); _FORCE_INLINE_ void _multimesh_update_motion_vectors_data_cache(MultiMesh *multimesh); _FORCE_INLINE_ void _multimesh_mark_dirty(MultiMesh *multimesh, int p_index, bool p_aabb); _FORCE_INLINE_ void _multimesh_mark_all_dirty(MultiMesh *multimesh, bool p_data, bool p_aabb); @@ -593,7 +594,6 @@ public: virtual AABB multimesh_get_aabb(RID p_multimesh) const override; void _update_dirty_multimeshes(); - bool _multimesh_enable_motion_vectors(RID p_multimesh); void _multimesh_get_motion_vectors_offsets(RID p_multimesh, uint32_t &r_current_offset, uint32_t &r_prev_offset); _FORCE_INLINE_ RS::MultimeshTransformFormat multimesh_get_transform_format(RID p_multimesh) const { diff --git a/servers/rendering/renderer_viewport.cpp b/servers/rendering/renderer_viewport.cpp index 75371de814..5880bf3951 100644 --- a/servers/rendering/renderer_viewport.cpp +++ b/servers/rendering/renderer_viewport.cpp @@ -1194,6 +1194,7 @@ void RendererViewport::viewport_set_use_taa(RID p_viewport, bool p_use_taa) { return; } viewport->use_taa = p_use_taa; + num_viewports_with_motion_vectors += p_use_taa ? 1 : -1; _configure_3d_render_buffers(viewport); } @@ -1378,6 +1379,10 @@ bool RendererViewport::free(RID p_rid) { RendererSceneOcclusionCull::get_singleton()->remove_buffer(p_rid); } + if (viewport->use_taa) { + num_viewports_with_motion_vectors--; + } + viewport_owner.free(p_rid); return true; @@ -1433,6 +1438,10 @@ int RendererViewport::get_total_draw_calls_used() const { return total_draw_calls_used; } +int RendererViewport::get_num_viewports_with_motion_vectors() const { + return num_viewports_with_motion_vectors; +} + RendererViewport::RendererViewport() { occlusion_rays_per_thread = GLOBAL_GET("rendering/occlusion_culling/occlusion_rays_per_thread"); } diff --git a/servers/rendering/renderer_viewport.h b/servers/rendering/renderer_viewport.h index 92c5929796..3bfb1afd51 100644 --- a/servers/rendering/renderer_viewport.h +++ b/servers/rendering/renderer_viewport.h @@ -198,6 +198,8 @@ public: int total_vertices_drawn = 0; int total_draw_calls_used = 0; + int num_viewports_with_motion_vectors = 0; + private: Vector<Viewport *> _sort_active_viewports(); void _viewport_set_size(Viewport *p_viewport, int p_width, int p_height, uint32_t p_view_count); @@ -302,6 +304,7 @@ public: int get_total_objects_drawn() const; int get_total_primitives_drawn() const; int get_total_draw_calls_used() const; + int get_num_viewports_with_motion_vectors() const; // Workaround for setting this on thread. void call_set_vsync_mode(DisplayServer::VSyncMode p_mode, DisplayServer::WindowID p_window); diff --git a/tests/core/object/test_class_db.h b/tests/core/object/test_class_db.h index 3f091fd2fe..5f7de11c71 100644 --- a/tests/core/object/test_class_db.h +++ b/tests/core/object/test_class_db.h @@ -409,9 +409,6 @@ void validate_method(const Context &p_context, const ExposedClass &p_class, cons if (p_method.return_type.name != StringName()) { const ExposedClass *return_class = p_context.find_exposed_class(p_method.return_type); if (return_class) { - TEST_COND(return_class->is_singleton, - "Method return type is a singleton: '", p_class.name, ".", p_method.name, "'."); - if (p_class.api_type == ClassDB::API_CORE) { TEST_COND(return_class->api_type == ClassDB::API_EDITOR, "Method '", p_class.name, ".", p_method.name, "' has return type '", return_class->name, diff --git a/tests/scene/test_packed_scene.h b/tests/scene/test_packed_scene.h index 9e525ab5bf..3517aba31f 100644 --- a/tests/scene/test_packed_scene.h +++ b/tests/scene/test_packed_scene.h @@ -109,6 +109,47 @@ TEST_CASE("[PackedScene] Instantiate Packed Scene") { memdelete(instance); } +TEST_CASE("[PackedScene] Instantiate Packed Scene With Children") { + // Create a scene to pack. + Node *scene = memnew(Node); + scene->set_name("TestScene"); + + // Add persisting child nodes to the scene. + Node *child1 = memnew(Node); + child1->set_name("Child1"); + scene->add_child(child1); + child1->set_owner(scene); + + Node *child2 = memnew(Node); + child2->set_name("Child2"); + scene->add_child(child2); + child2->set_owner(scene); + + // Add non persisting child node to the scene. + Node *child3 = memnew(Node); + child3->set_name("Child3"); + scene->add_child(child3); + + // Pack the scene. + PackedScene packed_scene; + packed_scene.pack(scene); + + // Instantiate the packed scene. + Node *instance = packed_scene.instantiate(); + CHECK(instance != nullptr); + CHECK(instance->get_name() == "TestScene"); + + // Validate the child nodes of the instantiated scene. + CHECK(instance->get_child_count() == 2); + CHECK(instance->get_child(0)->get_name() == "Child1"); + CHECK(instance->get_child(1)->get_name() == "Child2"); + CHECK(instance->get_child(0)->get_owner() == instance); + CHECK(instance->get_child(1)->get_owner() == instance); + + memdelete(scene); + memdelete(instance); +} + } // namespace TestPackedScene #endif // TEST_PACKED_SCENE_H |
