diff options
104 files changed, 1791 insertions, 1021 deletions
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 80a2703c2f..7ef1ce74ed 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -182,8 +182,20 @@ public: // Construct a placeholder. Object *obj = native_parent->creation_func(); + + // ClassDB::set_object_extension_instance() won't be called for placeholders. + // We need need to make sure that all the things it would have done (even if + // done in a different way to support placeholders) will also be done here. + obj->_extension = ClassDB::get_placeholder_extension(ti->name); obj->_extension_instance = memnew(PlaceholderExtensionInstance(ti->name)); + +#ifdef TOOLS_ENABLED + if (obj->_extension->track_instance) { + obj->_extension->track_instance(obj->_extension->tracking_userdata, obj); + } +#endif + return obj; } @@ -506,14 +518,7 @@ Object *ClassDB::_instantiate_internal(const StringName &p_class, bool p_require extension = get_placeholder_extension(ti->name); } #endif - Object *obj = (Object *)extension->create_instance(extension->class_userdata); - -#ifdef TOOLS_ENABLED - if (extension->track_instance) { - extension->track_instance(extension->tracking_userdata, obj); - } -#endif - return obj; + return (Object *)extension->create_instance(extension->class_userdata); } else { #ifdef TOOLS_ENABLED if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) { @@ -638,6 +643,12 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName & p_object->_extension = ti->gdextension; p_object->_extension_instance = p_instance; + +#ifdef TOOLS_ENABLED + if (p_object->_extension->track_instance) { + p_object->_extension->track_instance(p_object->_extension->tracking_userdata, p_object); + } +#endif } bool ClassDB::can_instantiate(const StringName &p_class) { diff --git a/core/object/object.cpp b/core/object/object.cpp index 06f6e8e9e6..f8d2feb5a8 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1100,6 +1100,20 @@ bool Object::_has_user_signal(const StringName &p_name) const { return signal_map[p_name].user.name.length() > 0; } +void Object::_remove_user_signal(const StringName &p_name) { + SignalData *s = signal_map.getptr(p_name); + ERR_FAIL_NULL_MSG(s, "Provided signal does not exist."); + ERR_FAIL_COND_MSG(!s->removable, "Signal is not removable (not added with add_user_signal)."); + for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) { + Object *target = slot_kv.key.get_object(); + if (likely(target)) { + target->connections.erase(slot_kv.value.cE); + } + } + + signal_map.erase(p_name); +} + Error Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (unlikely(p_argcount < 1)) { r_error.error = Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS; @@ -1248,6 +1262,10 @@ void Object::_add_user_signal(const String &p_name, const Array &p_args) { } add_user_signal(mi); + + if (signal_map.has(p_name)) { + signal_map.getptr(p_name)->removable = true; + } } TypedArray<Dictionary> Object::_get_signal_list() const { @@ -1661,6 +1679,7 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("add_user_signal", "signal", "arguments"), &Object::_add_user_signal, DEFVAL(Array())); ClassDB::bind_method(D_METHOD("has_user_signal", "signal"), &Object::_has_user_signal); + ClassDB::bind_method(D_METHOD("remove_user_signal", "signal"), &Object::_remove_user_signal); { MethodInfo mi; diff --git a/core/object/object.h b/core/object/object.h index d9551ecd01..915c3a8c25 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -619,6 +619,7 @@ private: MethodInfo user; HashMap<Callable, Slot, HashableHasher<Callable>> slot_map; + bool removable = false; }; HashMap<StringName, SignalData> signal_map; @@ -646,6 +647,7 @@ private: void _add_user_signal(const String &p_name, const Array &p_args = Array()); bool _has_user_signal(const StringName &p_name) const; + void _remove_user_signal(const StringName &p_name); Error _emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error); TypedArray<Dictionary> _get_signal_list() const; TypedArray<Dictionary> _get_signal_connection_list(const StringName &p_signal) const; diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index 660e13e819..bd3199ca0a 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -51,7 +51,7 @@ void Script::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POSTINITIALIZE: { if (EngineDebugger::is_active()) { - EngineDebugger::get_script_debugger()->set_break_language(get_language()); + callable_mp(this, &Script::_set_debugger_break_language).call_deferred(); } } break; } @@ -103,6 +103,12 @@ Dictionary Script::_get_script_constant_map() { return ret; } +void Script::_set_debugger_break_language() { + if (EngineDebugger::is_active()) { + EngineDebugger::get_script_debugger()->set_break_language(get_language()); + } +} + int Script::get_script_method_argument_count(const StringName &p_method, bool *r_is_valid) const { MethodInfo mi = get_method_info(p_method); diff --git a/core/object/script_language.h b/core/object/script_language.h index c6c6f3de9f..223f114150 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -124,6 +124,8 @@ protected: TypedArray<Dictionary> _get_script_signal_list(); Dictionary _get_script_constant_map(); + void _set_debugger_break_language(); + public: virtual void reload_from_file() override; diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index e0b8730a67..9c9e0fa899 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -72,6 +72,9 @@ void WorkerThreadPool::_process_task(Task *p_task) { p_task->pool_thread_index = pool_thread_index; prev_task = curr_thread.current_task; curr_thread.current_task = p_task; + if (p_task->pending_notify_yield_over) { + curr_thread.yield_is_over = true; + } task_mutex.unlock(); } #endif @@ -491,7 +494,11 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { ERR_FAIL_MSG("Invalid Task ID."); } Task *task = *taskp; - if (task->completed) { + if (task->pool_thread_index == -1) { // Completed or not started yet. + if (!task->completed) { + // This avoids a race condition where a task is created and yield-over called before it's processed. + task->pending_notify_yield_over = true; + } task_mutex.unlock(); return; } diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index 64f24df79f..a9cf260a0f 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -81,7 +81,8 @@ private: void *native_func_userdata = nullptr; String description; Semaphore done_semaphore; // For user threads awaiting. - bool completed = false; + bool completed : 1; + bool pending_notify_yield_over : 1; Group *group = nullptr; SelfList<Task> task_elem; uint32_t waiting_pool = 0; @@ -92,6 +93,8 @@ private: void free_template_userdata(); Task() : + completed(false), + pending_notify_yield_over(false), task_elem(this) {} }; diff --git a/doc/classes/AcceptDialog.xml b/doc/classes/AcceptDialog.xml index 7fbdd4272d..392364425b 100644 --- a/doc/classes/AcceptDialog.xml +++ b/doc/classes/AcceptDialog.xml @@ -100,6 +100,12 @@ </signal> </signals> <theme_items> + <theme_item name="buttons_min_height" data_type="constant" type="int" default="0"> + The minimum height of each button in the bottom row (such as OK/Cancel) in pixels. This can be increased to make buttons with short texts easier to click/tap. + </theme_item> + <theme_item name="buttons_min_width" data_type="constant" type="int" default="0"> + The minimum width of each button in the bottom row (such as OK/Cancel) in pixels. This can be increased to make buttons with short texts easier to click/tap. + </theme_item> <theme_item name="buttons_separation" data_type="constant" type="int" default="10"> The size of the vertical space between the dialog's content and the button row. </theme_item> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 20ee65403c..93a7b09fce 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -384,6 +384,9 @@ <member name="editors/3d/selection_box_color" type="Color" setter="" getter=""> The color to use for the selection box that surrounds selected nodes in the 3D editor viewport. The color's alpha channel influences the selection box's opacity. </member> + <member name="editors/3d_gizmos/gizmo_colors/aabb" type="Color" setter="" getter=""> + The color to use for the AABB gizmo that displays the [GeometryInstance3D]'s custom [AABB]. + </member> <member name="editors/3d_gizmos/gizmo_colors/instantiated" type="Color" setter="" getter=""> The color override to use for 3D editor gizmos if the [Node3D] in question is part of an instantiated scene file (from the perspective of the current scene). </member> diff --git a/doc/classes/FileSystemDock.xml b/doc/classes/FileSystemDock.xml index 9028dd4b9f..b3dc51ffaa 100644 --- a/doc/classes/FileSystemDock.xml +++ b/doc/classes/FileSystemDock.xml @@ -51,6 +51,11 @@ Emitted when a file is moved from [param old_file] path to [param new_file] path. </description> </signal> + <signal name="folder_color_changed"> + <description> + Emitted when folders change color. + </description> + </signal> <signal name="folder_moved"> <param index="0" name="old_folder" type="String" /> <param index="1" name="new_folder" type="String" /> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index c508591093..961cb2e684 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -357,7 +357,7 @@ <param index="0" name="signal" type="String" /> <param index="1" name="arguments" type="Array" default="[]" /> <description> - Adds a user-defined [param signal]. Optional arguments for the signal can be added as an [Array] of dictionaries, each defining a [code]name[/code] [String] and a [code]type[/code] [int] (see [enum Variant.Type]). See also [method has_user_signal]. + Adds a user-defined [param signal]. Optional arguments for the signal can be added as an [Array] of dictionaries, each defining a [code]name[/code] [String] and a [code]type[/code] [int] (see [enum Variant.Type]). See also [method has_user_signal] and [method remove_user_signal]. [codeblocks] [gdscript] add_user_signal("hurt", [ @@ -797,7 +797,7 @@ <return type="bool" /> <param index="0" name="signal" type="StringName" /> <description> - Returns [code]true[/code] if the given user-defined [param signal] name exists. Only signals added with [method add_user_signal] are included. + Returns [code]true[/code] if the given user-defined [param signal] name exists. Only signals added with [method add_user_signal] are included. See also [method remove_user_signal]. </description> </method> <method name="is_blocking_signals" qualifiers="const"> @@ -905,6 +905,13 @@ [b]Note:[/b] Metadata that has a name starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the Inspector and should not be edited, although it can still be found by this method. </description> </method> + <method name="remove_user_signal" experimental=""> + <return type="void" /> + <param index="0" name="signal" type="StringName" /> + <description> + Removes the given user signal [param signal] from the object. See also [method add_user_signal] and [method has_user_signal]. + </description> + </method> <method name="set"> <return type="void" /> <param index="0" name="property" type="StringName" /> diff --git a/doc/classes/SkeletonIK3D.xml b/doc/classes/SkeletonIK3D.xml index 6de6d9d186..4858a6ce22 100644 --- a/doc/classes/SkeletonIK3D.xml +++ b/doc/classes/SkeletonIK3D.xml @@ -55,6 +55,9 @@ </method> </methods> <members> + <member name="interpolation" type="float" setter="set_interpolation" getter="get_interpolation" deprecated="Use [member SkeletonModifier3D.influence] instead."> + Interpolation value for how much the IK results are applied to the current skeleton bone chain. A value of [code]1.0[/code] will overwrite all skeleton bone transforms completely while a value of [code]0.0[/code] will visually disable the SkeletonIK. + </member> <member name="magnet" type="Vector3" setter="set_magnet_position" getter="get_magnet_position" default="Vector3(0, 0, 0)"> Secondary target position (first is [member target] property or [member target_node]) for the IK chain. Use magnet position (pole target) to control the bending of the IK chain. Only works if the bone chain has more than 2 bones. The middle chain bone position will be linearly interpolated with the magnet position. </member> diff --git a/doc/classes/VisualShaderNodeComment.xml b/doc/classes/VisualShaderNodeComment.xml new file mode 100644 index 0000000000..28496a715a --- /dev/null +++ b/doc/classes/VisualShaderNodeComment.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeComment" inherits="VisualShaderNodeFrame" deprecated="This class has no function anymore and only exists for compatibility." xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Only exists for compatibility. Use [VisualShaderNodeFrame] as a replacement. + </brief_description> + <description> + This node was replaced by [VisualShaderNodeFrame] and only exists to preserve compatibility. In the [VisualShader] editor it behaves exactly like [VisualShaderNodeFrame]. + </description> + <tutorials> + </tutorials> + <members> + <member name="description" type="String" setter="set_description" getter="get_description" default=""""> + This property only exists to preserve data authored in earlier versions of Godot. It has currently no function. + </member> + </members> +</class> diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index de5f9ecf89..f57e9cb5f8 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -47,6 +47,7 @@ #include "scene/gui/check_box.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/margin_container.h" #include "scene/gui/option_button.h" #include "scene/gui/popup_menu.h" #include "scene/gui/spin_box.h" @@ -872,7 +873,13 @@ ConnectDialog::~ConnectDialog() { Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const { // If it's not a doc tooltip, fallback to the default one. - return p_text.contains("::") ? nullptr : memnew(EditorHelpTooltip(p_text)); + if (p_text.contains("::")) { + return nullptr; + } + + EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text)); + EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<ConnectionsDockTree *>(this)); + return memnew(Control); // Make the standard tooltip invisible. } struct _ConnectionsDockMethodInfoSort { @@ -1458,8 +1465,8 @@ void ConnectionsDock::update_tree() { section_item = tree->create_item(root); section_item->set_text(0, class_name); - // `|` separators used in `EditorHelpTooltip` for formatting. - section_item->set_tooltip_text(0, "class|" + doc_class_name + "||"); + // `|` separators used in `EditorHelpBit`. + section_item->set_tooltip_text(0, "class|" + doc_class_name + "|"); section_item->set_icon(0, class_icon); section_item->set_selectable(0, false); section_item->set_editable(0, false); @@ -1490,8 +1497,8 @@ void ConnectionsDock::update_tree() { sinfo["args"] = argnames; signal_item->set_metadata(0, sinfo); signal_item->set_icon(0, get_editor_theme_icon(SNAME("Signal"))); - // `|` separators used in `EditorHelpTooltip` for formatting. - signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name) + "|" + signame.trim_prefix(mi.name)); + // `|` separators used in `EditorHelpBit`. + signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name)); // List existing connections. List<Object::Connection> existing_connections; diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index f644d3335a..78f1b76e23 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -191,7 +191,7 @@ public: ////////////////////////////////////////// -// Custom `Tree` needed to use `EditorHelpTooltip` to display signal documentation. +// Custom `Tree` needed to use `EditorHelpBit` to display signal documentation. class ConnectionsDockTree : public Tree { virtual Control *make_custom_tooltip(const String &p_text) const; }; diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index f7914d3aaa..b00f059b36 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -202,8 +202,7 @@ void CreateDialog::_update_search() { select_type(_top_result(candidates, search_text)); } else { favorite->set_disabled(true); - help_bit->set_text(vformat(TTR("No results for \"%s\"."), search_text)); - help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5)); + help_bit->set_custom_text(String(), String(), vformat(TTR("No results for \"%s\"."), search_text.replace("[", "[lb]"))); get_ok_button()->set_disabled(true); search_options->deselect_all(); } @@ -502,17 +501,7 @@ void CreateDialog::select_type(const String &p_type, bool p_center_on_item) { to_select->select(0); search_options->scroll_to_item(to_select, p_center_on_item); - String text = help_bit->get_class_description(p_type); - if (!text.is_empty()) { - // Display both class name and description, since the help bit may be displayed - // far away from the location (especially if the dialog was resized to be taller). - help_bit->set_text(vformat("[b]%s[/b]: %s", p_type, text)); - help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); - } else { - // Use nested `vformat()` as translators shouldn't interfere with BBCode tags. - help_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", p_type))); - help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5)); - } + help_bit->parse_symbol("class|" + p_type + "|"); favorite->set_disabled(false); favorite->set_pressed(favorite_list.has(p_type)); @@ -837,6 +826,7 @@ CreateDialog::CreateDialog() { vbc->add_margin_child(TTR("Matches:"), search_options, true); help_bit = memnew(EditorHelpBit); + help_bit->set_content_height_limits(64 * EDSCALE, 64 * EDSCALE); help_bit->connect("request_hide", callable_mp(this, &CreateDialog::_hide_requested)); vbc->add_margin_child(TTR("Description:"), help_bit); diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp index c6ecaba230..c1db674cbe 100644 --- a/editor/editor_build_profile.cpp +++ b/editor/editor_build_profile.cpp @@ -593,6 +593,10 @@ void EditorBuildProfileManager::_action_confirm() { } } +void EditorBuildProfileManager::_hide_requested() { + _cancel_pressed(); // From AcceptDialog. +} + void EditorBuildProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) { TreeItem *class_item = class_list->create_item(p_parent); class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); @@ -646,21 +650,10 @@ void EditorBuildProfileManager::_class_list_item_selected() { Variant md = item->get_metadata(0); if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { - String text = description_bit->get_class_description(md); - if (!text.is_empty()) { - // Display both class name and description, since the help bit may be displayed - // far away from the location (especially if the dialog was resized to be taller). - description_bit->set_text(vformat("[b]%s[/b]: %s", md, text)); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); - } else { - // Use nested `vformat()` as translators shouldn't interfere with BBCode tags. - description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md))); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5)); - } + description_bit->parse_symbol("class|" + md.operator String() + "|"); } else if (md.get_type() == Variant::INT) { String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption((int)md)); - description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(build_option_description))); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); + description_bit->set_custom_text(TTR(item->get_text(0)), String(), TTRGET(build_option_description)); } } @@ -864,7 +857,8 @@ EditorBuildProfileManager::EditorBuildProfileManager() { main_vbc->add_margin_child(TTR("Configure Engine Compilation Profile:"), class_list, true); description_bit = memnew(EditorHelpBit); - description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE); + description_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE); + description_bit->connect("request_hide", callable_mp(this, &EditorBuildProfileManager::_hide_requested)); main_vbc->add_margin_child(TTR("Description:"), description_bit, false); confirm_dialog = memnew(ConfirmationDialog); diff --git a/editor/editor_build_profile.h b/editor/editor_build_profile.h index 649784afc3..a947365c7f 100644 --- a/editor/editor_build_profile.h +++ b/editor/editor_build_profile.h @@ -150,6 +150,7 @@ class EditorBuildProfileManager : public AcceptDialog { void _profile_action(int p_action); void _action_confirm(); + void _hide_requested(); void _update_edited_profile(); void _fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected); diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index 541bcd5e02..86b7b3eb2f 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -493,6 +493,10 @@ void EditorFeatureProfileManager::_profile_selected(int p_what) { _update_selected_profile(); } +void EditorFeatureProfileManager::_hide_requested() { + _cancel_pressed(); // From AcceptDialog. +} + void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const String &p_class, const String &p_selected) { TreeItem *class_item = class_list->create_item(p_parent); class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); @@ -555,22 +559,10 @@ void EditorFeatureProfileManager::_class_list_item_selected() { Variant md = item->get_metadata(0); if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { - String text = description_bit->get_class_description(md); - if (!text.is_empty()) { - // Display both class name and description, since the help bit may be displayed - // far away from the location (especially if the dialog was resized to be taller). - description_bit->set_text(vformat("[b]%s[/b]: %s", md, text)); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); - } else { - // Use nested `vformat()` as translators shouldn't interfere with BBCode tags. - description_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", md))); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5)); - } + description_bit->parse_symbol("class|" + md.operator String() + "|"); } else if (md.get_type() == Variant::INT) { String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature((int)md)); - description_bit->set_text(vformat("[b]%s[/b]: %s", TTR(item->get_text(0)), TTRGET(feature_description))); - description_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); - + description_bit->set_custom_text(TTR(item->get_text(0)), String(), TTRGET(feature_description)); return; } else { return; @@ -991,8 +983,9 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() { property_list_vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); description_bit = memnew(EditorHelpBit); + description_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE); + description_bit->connect("request_hide", callable_mp(this, &EditorFeatureProfileManager::_hide_requested)); property_list_vbc->add_margin_child(TTR("Description:"), description_bit, false); - description_bit->set_custom_minimum_size(Size2(0, 80) * EDSCALE); property_list = memnew(Tree); property_list_vbc->add_margin_child(TTR("Extra Options:"), property_list, true); diff --git a/editor/editor_feature_profile.h b/editor/editor_feature_profile.h index 25ee1c9ba4..2fa6ae9813 100644 --- a/editor/editor_feature_profile.h +++ b/editor/editor_feature_profile.h @@ -142,6 +142,7 @@ class EditorFeatureProfileManager : public AcceptDialog { void _profile_action(int p_action); void _profile_selected(int p_what); + void _hide_requested(); String current_profile; void _update_profile_list(const String &p_select_profile = String()); diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 1f7505633b..5cc09b7104 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -30,6 +30,7 @@ #include "editor_help.h" +#include "core/config/project_settings.h" #include "core/core_constants.h" #include "core/extension/gdextension.h" #include "core/input/input.h" @@ -193,7 +194,6 @@ void EditorHelp::_update_theme_item_cache() { class_desc->add_theme_font_override("normal_font", theme_cache.doc_font); class_desc->add_theme_font_size_override("normal_font_size", theme_cache.doc_font_size); - class_desc->add_theme_color_override("selection_color", get_theme_color(SNAME("selection_color"), SNAME("EditorHelp"))); class_desc->add_theme_constant_override("line_separation", get_theme_constant(SNAME("line_separation"), SNAME("EditorHelp"))); class_desc->add_theme_constant_override("table_h_separation", get_theme_constant(SNAME("table_h_separation"), SNAME("EditorHelp"))); class_desc->add_theme_constant_override("table_v_separation", get_theme_constant(SNAME("table_v_separation"), SNAME("EditorHelp"))); @@ -222,29 +222,35 @@ void EditorHelp::_class_list_select(const String &p_select) { } void EditorHelp::_class_desc_select(const String &p_select) { - if (p_select.begins_with("$")) { // enum - String select = p_select.substr(1, p_select.length()); - String class_name; - int rfind = select.rfind("."); - if (rfind != -1) { - class_name = select.substr(0, rfind); - select = select.substr(rfind + 1); + if (p_select.begins_with("$")) { // Enum. + const String link = p_select.substr(1); + + String enum_class_name; + String enum_name; + if (CoreConstants::is_global_enum(link)) { + enum_class_name = "@GlobalScope"; + enum_name = link; } else { - class_name = "@GlobalScope"; + const int dot_pos = link.rfind("."); + if (dot_pos >= 0) { + enum_class_name = link.left(dot_pos); + enum_name = link.substr(dot_pos + 1); + } else { + enum_class_name = edited_class; + enum_name = link; + } } - emit_signal(SNAME("go_to_help"), "class_enum:" + class_name + ":" + select); - return; - } else if (p_select.begins_with("#")) { - emit_signal(SNAME("go_to_help"), "class_name:" + p_select.substr(1, p_select.length())); - return; - } else if (p_select.begins_with("@")) { - int tag_end = p_select.find_char(' '); - String tag = p_select.substr(1, tag_end - 1); - String link = p_select.substr(tag_end + 1, p_select.length()).lstrip(" "); + emit_signal(SNAME("go_to_help"), "class_enum:" + enum_class_name + ":" + enum_name); + } else if (p_select.begins_with("#")) { // Class. + emit_signal(SNAME("go_to_help"), "class_name:" + p_select.substr(1)); + } else if (p_select.begins_with("@")) { // Member. + const int tag_end = p_select.find_char(' '); + const String tag = p_select.substr(1, tag_end - 1); + const String link = p_select.substr(tag_end + 1).lstrip(" "); String topic; - HashMap<String, int> *table = nullptr; + const HashMap<String, int> *table = nullptr; if (tag == "method") { topic = "class_method"; @@ -311,14 +317,14 @@ void EditorHelp::_class_desc_select(const String &p_select) { } if (link.contains(".")) { - int class_end = link.find_char('.'); - emit_signal(SNAME("go_to_help"), topic + ":" + link.substr(0, class_end) + ":" + link.substr(class_end + 1, link.length())); + const int class_end = link.find_char('.'); + emit_signal(SNAME("go_to_help"), topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1)); } } - } else if (p_select.begins_with("http")) { + } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) { OS::get_singleton()->shell_open(p_select); - } else if (p_select.begins_with("^")) { - DisplayServer::get_singleton()->clipboard_set(p_select.trim_prefix("^")); + } else if (p_select.begins_with("^")) { // Copy button. + DisplayServer::get_singleton()->clipboard_set(p_select.substr(1)); } } @@ -341,13 +347,15 @@ void EditorHelp::_class_desc_resized(bool p_force_update_theme) { } } -void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is_bitfield) { +static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_is_bitfield, RichTextLabel *p_rt, const Control *p_owner_node, const String &p_class) { + const Color type_color = p_owner_node->get_theme_color(SNAME("type_color"), SNAME("EditorHelp")); + if (p_type.is_empty() || p_type == "void") { - class_desc->push_color(Color(theme_cache.type_color, 0.5)); - class_desc->push_hint(TTR("No return value.")); - class_desc->add_text("void"); - class_desc->pop(); // hint - class_desc->pop(); // color + p_rt->push_color(Color(type_color, 0.5)); + p_rt->push_hint(TTR("No return value.")); + p_rt->add_text("void"); + p_rt->pop(); // hint + p_rt->pop(); // color return; } @@ -359,12 +367,12 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is String display_t; // For display purposes. if (is_enum_type) { link_t = p_enum; // The link for enums is always the full enum description - display_t = _contextualize_class_specifier(p_enum, edited_class); + display_t = _contextualize_class_specifier(p_enum, p_class); } else { - display_t = _contextualize_class_specifier(p_type, edited_class); + display_t = _contextualize_class_specifier(p_type, p_class); } - class_desc->push_color(theme_cache.type_color); + p_rt->push_color(type_color); bool add_array = false; if (can_ref) { if (link_t.ends_with("[]")) { @@ -372,37 +380,41 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is link_t = link_t.trim_suffix("[]"); display_t = display_t.trim_suffix("[]"); - class_desc->push_meta("#Array", RichTextLabel::META_UNDERLINE_ON_HOVER); // class - class_desc->add_text("Array"); - class_desc->pop(); // meta - class_desc->add_text("["); + p_rt->push_meta("#Array", RichTextLabel::META_UNDERLINE_ON_HOVER); // class + p_rt->add_text("Array"); + p_rt->pop(); // meta + p_rt->add_text("["); } else if (is_bitfield) { - class_desc->push_color(Color(theme_cache.type_color, 0.5)); - class_desc->push_hint(TTR("This value is an integer composed as a bitmask of the following flags.")); - class_desc->add_text("BitField"); - class_desc->pop(); // hint - class_desc->add_text("["); - class_desc->pop(); // color + p_rt->push_color(Color(type_color, 0.5)); + p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags.")); + p_rt->add_text("BitField"); + p_rt->pop(); // hint + p_rt->add_text("["); + p_rt->pop(); // color } if (is_enum_type) { - class_desc->push_meta("$" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // enum + p_rt->push_meta("$" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // enum } else { - class_desc->push_meta("#" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // class + p_rt->push_meta("#" + link_t, RichTextLabel::META_UNDERLINE_ON_HOVER); // class } } - class_desc->add_text(display_t); + p_rt->add_text(display_t); if (can_ref) { - class_desc->pop(); // meta + p_rt->pop(); // meta if (add_array) { - class_desc->add_text("]"); + p_rt->add_text("]"); } else if (is_bitfield) { - class_desc->push_color(Color(theme_cache.type_color, 0.5)); - class_desc->add_text("]"); - class_desc->pop(); // color + p_rt->push_color(Color(type_color, 0.5)); + p_rt->add_text("]"); + p_rt->pop(); // color } } - class_desc->pop(); // color + p_rt->pop(); // color +} + +void EditorHelp::_add_type(const String &p_type, const String &p_enum, bool p_is_bitfield) { + _add_type_to_rt(p_type, p_enum, p_is_bitfield, class_desc, this, edited_class); } void EditorHelp::_add_type_icon(const String &p_type, int p_size, const String &p_fallback) { @@ -717,10 +729,10 @@ void EditorHelp::_update_method_list(MethodType p_method_type, const Vector<DocD String group_prefix; for (int i = 0; i < m.size(); i++) { - const String new_prefix = m[i].name.substr(0, 3); + const String new_prefix = m[i].name.left(3); bool is_new_group = false; - if (i < m.size() - 1 && new_prefix == m[i + 1].name.substr(0, 3) && new_prefix != group_prefix) { + if (i < m.size() - 1 && new_prefix == m[i + 1].name.left(3) && new_prefix != group_prefix) { is_new_group = i > 0; group_prefix = new_prefix; } else if (!group_prefix.is_empty() && new_prefix != group_prefix) { @@ -748,7 +760,7 @@ void EditorHelp::_update_method_list(MethodType p_method_type, const Vector<DocD } void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc, MethodType p_method_type, const Vector<DocData::MethodDoc> &p_methods) { -#define DTR_DOC(m_string) (p_classdoc.is_script_doc ? (m_string) : DTR(m_string)) +#define HANDLE_DOC(m_string) ((p_classdoc.is_script_doc ? (m_string) : DTR(m_string)).strip_edges()) class_desc->add_newline(); class_desc->add_newline(); @@ -807,7 +819,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc TTRC("This constructor may be changed or removed in future versions."), TTRC("This operator may be changed or removed in future versions."), }; - DEPRECATED_DOC_MSG(DTR_DOC(method.deprecated_message), TTRGET(messages_by_type[p_method_type])); + DEPRECATED_DOC_MSG(HANDLE_DOC(method.deprecated_message), TTRGET(messages_by_type[p_method_type])); } if (method.is_experimental) { @@ -822,7 +834,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc TTRC("This constructor may be changed or removed in future versions."), TTRC("This operator may be changed or removed in future versions."), }; - EXPERIMENTAL_DOC_MSG(DTR_DOC(method.experimental_message), TTRGET(messages_by_type[p_method_type])); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(method.experimental_message), TTRGET(messages_by_type[p_method_type])); } if (!method.errors_returned.is_empty()) { @@ -856,7 +868,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc class_desc->pop(); // list } - const String descr = DTR_DOC(method.description).strip_edges(); + const String descr = HANDLE_DOC(method.description); const bool is_documented = method.is_deprecated || method.is_experimental || !descr.is_empty(); if (!descr.is_empty()) { if (has_prev_text) { @@ -903,7 +915,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc } } -#undef DTR_DOC +#undef HANDLE_DOC } void EditorHelp::_update_doc() { @@ -922,7 +934,7 @@ void EditorHelp::_update_doc() { DocData::ClassDoc cd = doc->class_list[edited_class]; // Make a copy, so we can sort without worrying. -#define DTR_DOC(m_string) (cd.is_script_doc ? (m_string) : DTR(m_string)) +#define HANDLE_DOC(m_string) ((cd.is_script_doc ? (m_string) : DTR(m_string)).strip_edges()) // Class name @@ -940,12 +952,12 @@ void EditorHelp::_update_doc() { if (cd.is_deprecated) { class_desc->add_newline(); - DEPRECATED_DOC_MSG(DTR_DOC(cd.deprecated_message), TTR("This class may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(cd.deprecated_message), TTR("This class may be changed or removed in future versions.")); } if (cd.is_experimental) { class_desc->add_newline(); - EXPERIMENTAL_DOC_MSG(DTR_DOC(cd.experimental_message), TTR("This class may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.experimental_message), TTR("This class may be changed or removed in future versions.")); } // Inheritance tree @@ -1003,7 +1015,7 @@ void EditorHelp::_update_doc() { bool has_description = false; // Brief description - const String brief_class_descr = DTR_DOC(cd.brief_description).strip_edges(); + const String brief_class_descr = HANDLE_DOC(cd.brief_description); if (!brief_class_descr.is_empty()) { has_description = true; @@ -1022,7 +1034,7 @@ void EditorHelp::_update_doc() { } // Class description - const String class_descr = DTR_DOC(cd.description).strip_edges(); + const String class_descr = HANDLE_DOC(cd.description); if (!class_descr.is_empty()) { has_description = true; @@ -1106,9 +1118,9 @@ void EditorHelp::_update_doc() { class_desc->push_color(theme_cache.symbol_color); for (const DocData::TutorialDoc &tutorial : cd.tutorials) { - const String link = DTR_DOC(tutorial.link).strip_edges(); + const String link = HANDLE_DOC(tutorial.link); - String link_text = DTR_DOC(tutorial.title).strip_edges(); + String link_text = HANDLE_DOC(tutorial.title); if (link_text.is_empty()) { const int sep_pos = link.find("//"); if (sep_pos >= 0) { @@ -1441,7 +1453,7 @@ void EditorHelp::_update_doc() { _push_normal_font(); class_desc->push_color(theme_cache.comment_color); - const String descr = DTR_DOC(theme_item.description).strip_edges(); + const String descr = HANDLE_DOC(theme_item.description); if (!descr.is_empty()) { _add_text(descr); } else { @@ -1538,13 +1550,13 @@ void EditorHelp::_update_doc() { _push_normal_font(); class_desc->push_color(theme_cache.comment_color); - const String descr = DTR_DOC(signal.description).strip_edges(); + const String descr = HANDLE_DOC(signal.description); const bool is_multiline = descr.find_char('\n') > 0; bool has_prev_text = false; if (signal.is_deprecated) { has_prev_text = true; - DEPRECATED_DOC_MSG(DTR_DOC(signal.deprecated_message), TTR("This signal may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(signal.deprecated_message), TTR("This signal may be changed or removed in future versions.")); } if (signal.is_experimental) { @@ -1555,7 +1567,7 @@ void EditorHelp::_update_doc() { } } has_prev_text = true; - EXPERIMENTAL_DOC_MSG(DTR_DOC(signal.experimental_message), TTR("This signal may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(signal.experimental_message), TTR("This signal may be changed or removed in future versions.")); } if (!descr.is_empty()) { @@ -1669,7 +1681,7 @@ void EditorHelp::_update_doc() { // Enum description. if (key != "@unnamed_enums" && cd.enums.has(key)) { - const String descr = DTR_DOC(cd.enums[key].description).strip_edges(); + const String descr = HANDLE_DOC(cd.enums[key].description); const bool is_multiline = descr.find_char('\n') > 0; if (cd.enums[key].is_deprecated || cd.enums[key].is_experimental || !descr.is_empty()) { class_desc->add_newline(); @@ -1682,7 +1694,7 @@ void EditorHelp::_update_doc() { if (cd.enums[key].is_deprecated) { has_prev_text = true; - DEPRECATED_DOC_MSG(DTR_DOC(cd.enums[key].deprecated_message), TTR("This enumeration may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(cd.enums[key].deprecated_message), TTR("This enumeration may be changed or removed in future versions.")); } if (cd.enums[key].is_experimental) { @@ -1693,7 +1705,7 @@ void EditorHelp::_update_doc() { } } has_prev_text = true; - EXPERIMENTAL_DOC_MSG(DTR_DOC(cd.enums[key].experimental_message), TTR("This enumeration may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.enums[key].experimental_message), TTR("This enumeration may be changed or removed in future versions.")); } if (!descr.is_empty()) { @@ -1718,7 +1730,7 @@ void EditorHelp::_update_doc() { bool prev_is_multiline = true; // Use a large margin for the first item. for (const DocData::ConstantDoc &enum_value : E.value) { - const String descr = DTR_DOC(enum_value.description).strip_edges(); + const String descr = HANDLE_DOC(enum_value.description); const bool is_multiline = descr.find_char('\n') > 0; class_desc->add_newline(); @@ -1766,7 +1778,7 @@ void EditorHelp::_update_doc() { if (enum_value.is_deprecated) { has_prev_text = true; - DEPRECATED_DOC_MSG(DTR_DOC(enum_value.deprecated_message), TTR("This constant may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(enum_value.deprecated_message), TTR("This constant may be changed or removed in future versions.")); } if (enum_value.is_experimental) { @@ -1777,7 +1789,7 @@ void EditorHelp::_update_doc() { } } has_prev_text = true; - EXPERIMENTAL_DOC_MSG(DTR_DOC(enum_value.experimental_message), TTR("This constant may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(enum_value.experimental_message), TTR("This constant may be changed or removed in future versions.")); } if (!descr.is_empty()) { @@ -1817,7 +1829,7 @@ void EditorHelp::_update_doc() { bool prev_is_multiline = true; // Use a large margin for the first item. for (const DocData::ConstantDoc &constant : constants) { - const String descr = DTR_DOC(constant.description).strip_edges(); + const String descr = HANDLE_DOC(constant.description); const bool is_multiline = descr.find_char('\n') > 0; class_desc->add_newline(); @@ -1871,7 +1883,7 @@ void EditorHelp::_update_doc() { if (constant.is_deprecated) { has_prev_text = true; - DEPRECATED_DOC_MSG(DTR_DOC(constant.deprecated_message), TTR("This constant may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(constant.deprecated_message), TTR("This constant may be changed or removed in future versions.")); } if (constant.is_experimental) { @@ -1882,7 +1894,7 @@ void EditorHelp::_update_doc() { } } has_prev_text = true; - EXPERIMENTAL_DOC_MSG(DTR_DOC(constant.experimental_message), TTR("This constant may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(constant.experimental_message), TTR("This constant may be changed or removed in future versions.")); } if (!descr.is_empty()) { @@ -2000,7 +2012,7 @@ void EditorHelp::_update_doc() { _push_normal_font(); class_desc->push_color(theme_cache.comment_color); - const String descr = DTR_DOC(annotation.description).strip_edges(); + const String descr = HANDLE_DOC(annotation.description); if (!descr.is_empty()) { _add_text(descr); } else { @@ -2185,7 +2197,7 @@ void EditorHelp::_update_doc() { if (prop.is_deprecated) { has_prev_text = true; - DEPRECATED_DOC_MSG(DTR_DOC(prop.deprecated_message), TTR("This property may be changed or removed in future versions.")); + DEPRECATED_DOC_MSG(HANDLE_DOC(prop.deprecated_message), TTR("This property may be changed or removed in future versions.")); } if (prop.is_experimental) { @@ -2194,10 +2206,10 @@ void EditorHelp::_update_doc() { class_desc->add_newline(); } has_prev_text = true; - EXPERIMENTAL_DOC_MSG(DTR_DOC(prop.experimental_message), TTR("This property may be changed or removed in future versions.")); + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(prop.experimental_message), TTR("This property may be changed or removed in future versions.")); } - const String descr = DTR_DOC(prop.description).strip_edges(); + const String descr = HANDLE_DOC(prop.description); if (!descr.is_empty()) { if (has_prev_text) { class_desc->add_newline(); @@ -2251,7 +2263,7 @@ void EditorHelp::_update_doc() { // Free the scroll. scroll_locked = false; -#undef DTR_DOC +#undef HANDLE_DOC } void EditorHelp::_request_help(const String &p_string) { @@ -2331,7 +2343,7 @@ void EditorHelp::_help_callback(const String &p_topic) { } } -static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control *p_owner_node, const String &p_class) { +static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const Control *p_owner_node, const String &p_class) { const DocTools *doc = EditorHelp::get_doc_data(); bool is_native = false; @@ -2452,7 +2464,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control const String tag = bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1); if (tag.begins_with("/")) { - bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1, tag.length()); + bool tag_ok = tag_stack.size() && tag_stack.front()->get() == tag.substr(1); if (!tag_ok) { p_rt->add_text("["); @@ -2467,8 +2479,8 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control } } else if (tag.begins_with("method ") || tag.begins_with("constructor ") || tag.begins_with("operator ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("annotation ") || tag.begins_with("theme_item ")) { const int tag_end = tag.find_char(' '); - const String link_tag = tag.substr(0, tag_end); - const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" "); + const String link_tag = tag.left(tag_end); + const String link_target = tag.substr(tag_end + 1).lstrip(" "); Color target_color = link_color; RichTextLabel::MetaUnderline underline_mode = RichTextLabel::META_UNDERLINE_ON_HOVER; @@ -2520,7 +2532,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = brk_end + 1; } else if (tag.begins_with("param ")) { const int tag_end = tag.find_char(' '); - const String param_name = tag.substr(tag_end + 1, tag.length()).lstrip(" "); + const String param_name = tag.substr(tag_end + 1).lstrip(" "); // Use monospace font with translucent background color to make code easier to distinguish from other text. p_rt->push_font(doc_code_font); @@ -2741,7 +2753,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("url=")) { - String url = tag.substr(4, tag.length()); + String url = tag.substr(4); p_rt->push_meta(url); pos = brk_end + 1; @@ -2751,13 +2763,13 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control int height = 0; bool size_in_percent = false; if (tag.length() > 4) { - Vector<String> subtags = tag.substr(4, tag.length()).split(" "); + Vector<String> subtags = tag.substr(4).split(" "); HashMap<String, String> bbcode_options; for (int i = 0; i < subtags.size(); i++) { const String &expr = subtags[i]; int value_pos = expr.find_char('='); if (value_pos > -1) { - bbcode_options[expr.substr(0, value_pos)] = expr.substr(value_pos + 1).unquote(); + bbcode_options[expr.left(value_pos)] = expr.substr(value_pos + 1).unquote(); } } HashMap<String, String>::Iterator width_option = bbcode_options.find("width"); @@ -2787,14 +2799,14 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control pos = end; tag_stack.push_front("img"); } else if (tag.begins_with("color=")) { - String col = tag.substr(6, tag.length()); + String col = tag.substr(6); Color color = Color::from_string(col, Color()); p_rt->push_color(color); pos = brk_end + 1; tag_stack.push_front("color"); } else if (tag.begins_with("font=")) { - String font_path = tag.substr(5, tag.length()); + String font_path = tag.substr(5); Ref<Font> font = ResourceLoader::load(font_path, "Font"); if (font.is_valid()) { p_rt->push_font(font); @@ -3120,68 +3132,50 @@ DocTools *EditorHelp::get_doc_data() { /// EditorHelpBit /// -void EditorHelpBit::_go_to_help(const String &p_what) { - EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); - ScriptEditor::get_singleton()->goto_help(p_what); - emit_signal(SNAME("request_hide")); -} +#define HANDLE_DOC(m_string) ((is_native ? DTR(m_string) : (m_string)).strip_edges()) -void EditorHelpBit::_meta_clicked(const String &p_select) { - if (p_select.begins_with("$")) { // enum - String select = p_select.substr(1, p_select.length()); - String class_name; - int rfind = select.rfind("."); - if (rfind != -1) { - class_name = select.substr(0, rfind); - select = select.substr(rfind + 1); - } else { - class_name = "@GlobalScope"; - } - _go_to_help("class_enum:" + class_name + ":" + select); - return; - } else if (p_select.begins_with("#")) { - _go_to_help("class_name:" + p_select.substr(1, p_select.length())); - return; - } else if (p_select.begins_with("@")) { - String m = p_select.substr(1, p_select.length()); - - if (m.contains(".")) { - _go_to_help("class_method:" + m.get_slice(".", 0) + ":" + m.get_slice(".", 0)); // Must go somewhere else. - } - } -} - -String EditorHelpBit::get_class_description(const StringName &p_class_name) const { +EditorHelpBit::HelpData EditorHelpBit::_get_class_help_data(const StringName &p_class_name) { if (doc_class_cache.has(p_class_name)) { return doc_class_cache[p_class_name]; } - String description; + HelpData result; const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); if (E) { // Non-native class shouldn't be cached, nor translated. const bool is_native = !E->value.is_script_doc; - description = is_native ? DTR(E->value.brief_description) : E->value.brief_description; + + result.description = HANDLE_DOC(E->value.brief_description); + if (E->value.is_deprecated) { + if (E->value.deprecated_message.is_empty()) { + result.deprecated_message = TTR("This class may be changed or removed in future versions."); + } else { + result.deprecated_message = HANDLE_DOC(E->value.deprecated_message); + } + } + if (E->value.is_experimental) { + if (E->value.experimental_message.is_empty()) { + result.experimental_message = TTR("This class may be changed or removed in future versions."); + } else { + result.experimental_message = HANDLE_DOC(E->value.experimental_message); + } + } if (is_native) { - doc_class_cache[p_class_name] = description; + doc_class_cache[p_class_name] = result; } } - return description; + return result; } -String EditorHelpBit::get_property_description(const StringName &p_class_name, const StringName &p_property_name) const { - if (!custom_description.is_empty()) { - return custom_description; - } - +EditorHelpBit::HelpData EditorHelpBit::_get_property_help_data(const StringName &p_class_name, const StringName &p_property_name) { if (doc_property_cache.has(p_class_name) && doc_property_cache[p_class_name].has(p_property_name)) { return doc_property_cache[p_class_name][p_property_name]; } - String description; + HelpData result; const DocTools *dd = EditorHelp::get_doc_data(); const HashMap<String, DocData::ClassDoc>::ConstIterator E = dd->class_list.find(p_class_name); @@ -3190,7 +3184,22 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c const bool is_native = !E->value.is_script_doc; for (const DocData::PropertyDoc &property : E->value.properties) { - String description_current = is_native ? DTR(property.description) : property.description; + HelpData current; + current.description = HANDLE_DOC(property.description); + if (property.is_deprecated) { + if (property.deprecated_message.is_empty()) { + current.deprecated_message = TTR("This property may be changed or removed in future versions."); + } else { + current.deprecated_message = HANDLE_DOC(property.deprecated_message); + } + } + if (property.is_experimental) { + if (property.experimental_message.is_empty()) { + current.experimental_message = TTR("This property may be changed or removed in future versions."); + } else { + current.experimental_message = HANDLE_DOC(property.experimental_message); + } + } String enum_class_name; String enum_name; @@ -3215,18 +3224,19 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c if (constant.enumeration == enum_name && !constant.name.ends_with("_MAX")) { // Prettify the enum value display, so that "<ENUM_NAME>_<ITEM>" becomes "Item". const String item_name = EditorPropertyNameProcessor::get_singleton()->process_name(constant.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED).trim_prefix(enum_prefix); - String item_descr = (is_native ? DTR(constant.description) : constant.description).strip_edges(); + String item_descr = HANDLE_DOC(constant.description); if (item_descr.is_empty()) { - item_descr = ("[i]" + DTR("No description available.") + "[/i]"); + item_descr = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]"; } - description_current += vformat("\n[b]%s:[/b] %s", item_name, item_descr); + current.description += vformat("\n[b]%s:[/b] %s", item_name, item_descr); } } + current.description = current.description.lstrip("\n"); } } if (property.name == p_property_name) { - description = description_current; + result = current; if (!is_native) { break; @@ -3234,20 +3244,20 @@ String EditorHelpBit::get_property_description(const StringName &p_class_name, c } if (is_native) { - doc_property_cache[p_class_name][property.name] = description_current; + doc_property_cache[p_class_name][property.name] = current; } } } - return description; + return result; } -String EditorHelpBit::get_method_description(const StringName &p_class_name, const StringName &p_method_name) const { +EditorHelpBit::HelpData EditorHelpBit::_get_method_help_data(const StringName &p_class_name, const StringName &p_method_name) { if (doc_method_cache.has(p_class_name) && doc_method_cache[p_class_name].has(p_method_name)) { return doc_method_cache[p_class_name][p_method_name]; } - String description; + HelpData result; const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); if (E) { @@ -3255,10 +3265,30 @@ String EditorHelpBit::get_method_description(const StringName &p_class_name, con const bool is_native = !E->value.is_script_doc; for (const DocData::MethodDoc &method : E->value.methods) { - String description_current = is_native ? DTR(method.description) : method.description; + HelpData current; + current.description = HANDLE_DOC(method.description); + if (method.is_deprecated) { + if (method.deprecated_message.is_empty()) { + current.deprecated_message = TTR("This method may be changed or removed in future versions."); + } else { + current.deprecated_message = HANDLE_DOC(method.deprecated_message); + } + } + if (method.is_experimental) { + if (method.experimental_message.is_empty()) { + current.experimental_message = TTR("This method may be changed or removed in future versions."); + } else { + current.experimental_message = HANDLE_DOC(method.experimental_message); + } + } + current.doc_type = { method.return_type, method.return_enum, method.return_is_bitfield }; + for (const DocData::ArgumentDoc &argument : method.arguments) { + const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield }; + current.arguments.push_back({ argument.name, argument_type, argument.default_value }); + } if (method.name == p_method_name) { - description = description_current; + result = current; if (!is_native) { break; @@ -3266,20 +3296,20 @@ String EditorHelpBit::get_method_description(const StringName &p_class_name, con } if (is_native) { - doc_method_cache[p_class_name][method.name] = description_current; + doc_method_cache[p_class_name][method.name] = current; } } } - return description; + return result; } -String EditorHelpBit::get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const { +EditorHelpBit::HelpData EditorHelpBit::_get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name) { if (doc_signal_cache.has(p_class_name) && doc_signal_cache[p_class_name].has(p_signal_name)) { return doc_signal_cache[p_class_name][p_signal_name]; } - String description; + HelpData result; const HashMap<String, DocData::ClassDoc>::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); if (E) { @@ -3287,10 +3317,29 @@ String EditorHelpBit::get_signal_description(const StringName &p_class_name, con const bool is_native = !E->value.is_script_doc; for (const DocData::MethodDoc &signal : E->value.signals) { - String description_current = is_native ? DTR(signal.description) : signal.description; + HelpData current; + current.description = HANDLE_DOC(signal.description); + if (signal.is_deprecated) { + if (signal.deprecated_message.is_empty()) { + current.deprecated_message = TTR("This signal may be changed or removed in future versions."); + } else { + current.deprecated_message = HANDLE_DOC(signal.deprecated_message); + } + } + if (signal.is_experimental) { + if (signal.experimental_message.is_empty()) { + current.experimental_message = TTR("This signal may be changed or removed in future versions."); + } else { + current.experimental_message = HANDLE_DOC(signal.experimental_message); + } + } + for (const DocData::ArgumentDoc &argument : signal.arguments) { + const DocType argument_type = { argument.type, argument.enumeration, argument.is_bitfield }; + current.arguments.push_back({ argument.name, argument_type, argument.default_value }); + } if (signal.name == p_signal_name) { - description = description_current; + result = current; if (!is_native) { break; @@ -3298,20 +3347,20 @@ String EditorHelpBit::get_signal_description(const StringName &p_class_name, con } if (is_native) { - doc_signal_cache[p_class_name][signal.name] = description_current; + doc_signal_cache[p_class_name][signal.name] = current; } } } - return description; + return result; } -String EditorHelpBit::get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const { +EditorHelpBit::HelpData EditorHelpBit::_get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name) { if (doc_theme_item_cache.has(p_class_name) && doc_theme_item_cache[p_class_name].has(p_theme_item_name)) { return doc_theme_item_cache[p_class_name][p_theme_item_name]; } - String description; + HelpData result; bool found = false; const DocTools *dd = EditorHelp::get_doc_data(); @@ -3321,142 +3370,477 @@ String EditorHelpBit::get_theme_item_description(const StringName &p_class_name, const bool is_native = !E->value.is_script_doc; for (const DocData::ThemeItemDoc &theme_item : E->value.theme_properties) { - String description_current = is_native ? DTR(theme_item.description) : theme_item.description; + HelpData current; + current.description = HANDLE_DOC(theme_item.description); if (theme_item.name == p_theme_item_name) { - description = description_current; + result = current; found = true; - if (!is_native) { break; } } if (is_native) { - doc_theme_item_cache[p_class_name][theme_item.name] = description_current; + doc_theme_item_cache[p_class_name][theme_item.name] = current; } } if (found || E->value.inherits.is_empty()) { break; } + // Check for inherited theme items. E = dd->class_list.find(E->value.inherits); } - return description; + return result; +} + +#undef HANDLE_DOC + +void EditorHelpBit::_add_type_to_title(const DocType &p_doc_type) { + _add_type_to_rt(p_doc_type.type, p_doc_type.enumeration, p_doc_type.is_bitfield, title, this, symbol_class_name); +} + +void EditorHelpBit::_update_labels() { + const Ref<Font> doc_bold_font = get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts)); + + if (!symbol_visible_type.is_empty() || !symbol_name.is_empty()) { + title->clear(); + + title->push_font(doc_bold_font); + + if (!symbol_visible_type.is_empty()) { + title->push_color(get_theme_color(SNAME("title_color"), SNAME("EditorHelp"))); + title->add_text(symbol_visible_type); + title->pop(); // color + } + + if (!symbol_visible_type.is_empty() && !symbol_name.is_empty()) { + title->add_text(" "); + } + + if (!symbol_name.is_empty()) { + title->push_underline(); + title->add_text(symbol_name); + title->pop(); // underline + } + + title->pop(); // font + + if (symbol_type == "method" || symbol_type == "signal") { + const Color symbol_color = get_theme_color(SNAME("symbol_color"), SNAME("EditorHelp")); + const Color value_color = get_theme_color(SNAME("value_color"), SNAME("EditorHelp")); + + title->push_font(get_theme_font(SNAME("doc_source"), EditorStringName(EditorFonts))); + title->push_font_size(get_theme_font_size(SNAME("doc_source_size"), EditorStringName(EditorFonts)) * 0.9); + + title->push_color(symbol_color); + title->add_text("("); + title->pop(); // color + + for (int i = 0; i < help_data.arguments.size(); i++) { + const ArgumentData &argument = help_data.arguments[i]; + + if (i > 0) { + title->push_color(symbol_color); + title->add_text(", "); + title->pop(); // color + } + + title->add_text(argument.name); + + title->push_color(symbol_color); + title->add_text(": "); + title->pop(); // color + + _add_type_to_title(argument.doc_type); + + if (!argument.default_value.is_empty()) { + title->push_color(symbol_color); + title->add_text(" = "); + title->pop(); // color + + title->push_color(value_color); + title->add_text(argument.default_value); + title->pop(); // color + } + } + + title->push_color(symbol_color); + title->add_text(")"); + title->pop(); // color + + if (symbol_type == "method") { + title->push_color(symbol_color); + title->add_text(" -> "); + title->pop(); // color + + _add_type_to_title(help_data.doc_type); + } + + title->pop(); // font_size + title->pop(); // font + } + + title->show(); + } else { + title->hide(); + } + + content->clear(); + + bool has_prev_text = false; + + if (!help_data.deprecated_message.is_empty()) { + has_prev_text = true; + + Ref<Texture2D> error_icon = get_editor_theme_icon(SNAME("StatusError")); + content->add_image(error_icon, error_icon->get_width(), error_icon->get_height()); + content->add_text(" "); + content->push_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor))); + content->push_font(doc_bold_font); + content->add_text(TTR("Deprecated:")); + content->pop(); // font + content->pop(); // color + content->add_text(" "); + _add_text_to_rt(help_data.deprecated_message, content, this, symbol_class_name); + } + + if (!help_data.experimental_message.is_empty()) { + if (has_prev_text) { + content->add_newline(); + content->add_newline(); + } + has_prev_text = true; + + Ref<Texture2D> warning_icon = get_editor_theme_icon(SNAME("NodeWarning")); + content->add_image(warning_icon, warning_icon->get_width(), warning_icon->get_height()); + content->add_text(" "); + content->push_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor))); + content->push_font(doc_bold_font); + content->add_text(TTR("Experimental:")); + content->pop(); // font + content->pop(); // color + content->add_text(" "); + _add_text_to_rt(help_data.experimental_message, content, this, symbol_class_name); + } + + if (!help_data.description.is_empty()) { + if (has_prev_text) { + content->add_newline(); + content->add_newline(); + } + has_prev_text = true; + + const Color comment_color = get_theme_color(SNAME("comment_color"), SNAME("EditorHelp")); + _add_text_to_rt(help_data.description.replace("<EditorHelpBitCommentColor>", comment_color.to_html()), content, this, symbol_class_name); + } + + if (is_inside_tree()) { + update_content_height(); + } +} + +void EditorHelpBit::_go_to_help(const String &p_what) { + EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); + ScriptEditor::get_singleton()->goto_help(p_what); + emit_signal(SNAME("request_hide")); +} + +void EditorHelpBit::_meta_clicked(const String &p_select) { + if (p_select.begins_with("$")) { // Enum. + const String link = p_select.substr(1); + + String enum_class_name; + String enum_name; + if (CoreConstants::is_global_enum(link)) { + enum_class_name = "@GlobalScope"; + enum_name = link; + } else { + const int dot_pos = link.rfind("."); + if (dot_pos >= 0) { + enum_class_name = link.left(dot_pos); + enum_name = link.substr(dot_pos + 1); + } else { + enum_class_name = symbol_class_name; + enum_name = link; + } + } + + _go_to_help("class_enum:" + enum_class_name + ":" + enum_name); + } else if (p_select.begins_with("#")) { // Class. + _go_to_help("class_name:" + p_select.substr(1)); + } else if (p_select.begins_with("@")) { // Member. + const int tag_end = p_select.find_char(' '); + const String tag = p_select.substr(1, tag_end - 1); + const String link = p_select.substr(tag_end + 1).lstrip(" "); + + String topic; + if (tag == "method") { + topic = "class_method"; + } else if (tag == "constructor") { + topic = "class_method"; + } else if (tag == "operator") { + topic = "class_method"; + } else if (tag == "member") { + topic = "class_property"; + } else if (tag == "enum") { + topic = "class_enum"; + } else if (tag == "signal") { + topic = "class_signal"; + } else if (tag == "constant") { + topic = "class_constant"; + } else if (tag == "annotation") { + topic = "class_annotation"; + } else if (tag == "theme_item") { + topic = "class_theme_item"; + } else { + return; + } + + if (link.contains(".")) { + const int class_end = link.find_char('.'); + _go_to_help(topic + ":" + link.left(class_end) + ":" + link.substr(class_end + 1)); + } else { + _go_to_help(topic + ":" + symbol_class_name + ":" + link); + } + } else if (p_select.begins_with("http:") || p_select.begins_with("https:")) { + OS::get_singleton()->shell_open(p_select); + } else if (p_select.begins_with("^")) { // Copy button. + DisplayServer::get_singleton()->clipboard_set(p_select.substr(1)); + } } void EditorHelpBit::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_text", "text"), &EditorHelpBit::set_text); ADD_SIGNAL(MethodInfo("request_hide")); } void EditorHelpBit::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_THEME_CHANGED: { - rich_text->add_theme_color_override("selection_color", get_theme_color(SNAME("selection_color"), SNAME("EditorHelp"))); - rich_text->clear(); - _add_text_to_rt(text, rich_text, this, doc_class_name); - rich_text->reset_size(); // Force recalculating size after parsing bbcode. - } break; + case NOTIFICATION_THEME_CHANGED: + _update_labels(); + break; } } -void EditorHelpBit::set_text(const String &p_text) { - text = p_text; - rich_text->clear(); - _add_text_to_rt(text, rich_text, this, doc_class_name); +void EditorHelpBit::parse_symbol(const String &p_symbol) { + const PackedStringArray slices = p_symbol.split("|", true, 2); + ERR_FAIL_COND_MSG(slices.size() < 3, "Invalid doc id. The expected format is 'item_type|class_name|item_name'."); + + const String &item_type = slices[0]; + const String &class_name = slices[1]; + const String &item_name = slices[2]; + + String visible_type; + String name = item_name; + + if (item_type == "class") { + visible_type = TTR("Class:"); + name = class_name; + help_data = _get_class_help_data(class_name); + } else if (item_type == "property") { + if (name.begins_with("metadata/")) { + visible_type = TTR("Metadata:"); + name = name.trim_prefix("metadata/"); + } else if (class_name == "ProjectSettings" || class_name == "EditorSettings") { + visible_type = TTR("Setting:"); + } else { + visible_type = TTR("Property:"); + } + help_data = _get_property_help_data(class_name, item_name); + } else if (item_type == "internal_property") { + visible_type = TTR("Internal Property:"); + help_data = HelpData(); + help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("This property can only be set in the Inspector.") + "[/i][/color]"; + } else if (item_type == "method") { + visible_type = TTR("Method:"); + help_data = _get_method_help_data(class_name, item_name); + } else if (item_type == "signal") { + visible_type = TTR("Signal:"); + help_data = _get_signal_help_data(class_name, item_name); + } else if (item_type == "theme_item") { + visible_type = TTR("Theme Property:"); + help_data = _get_theme_item_help_data(class_name, item_name); + } else { + ERR_FAIL_MSG("Invalid tooltip type '" + item_type + "'. Valid types are 'class', 'property', 'internal_property', 'method', 'signal', and 'theme_item'."); + } + + symbol_class_name = class_name; + symbol_type = item_type; + symbol_visible_type = visible_type; + symbol_name = name; + + if (help_data.description.is_empty()) { + help_data.description = "[color=<EditorHelpBitCommentColor>][i]" + TTR("No description available.") + "[/i][/color]"; + } + + if (is_inside_tree()) { + _update_labels(); + } } -EditorHelpBit::EditorHelpBit() { - rich_text = memnew(RichTextLabel); - add_child(rich_text); - rich_text->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); - rich_text->set_fit_content(true); - set_custom_minimum_size(Size2(0, 50 * EDSCALE)); +void EditorHelpBit::set_custom_text(const String &p_type, const String &p_name, const String &p_description) { + symbol_class_name = String(); + symbol_type = String(); + symbol_visible_type = p_type; + symbol_name = p_name; + + help_data = HelpData(); + help_data.description = p_description; + + if (is_inside_tree()) { + _update_labels(); + } } -/// EditorHelpTooltip /// +void EditorHelpBit::prepend_description(const String &p_text) { + if (help_data.description.is_empty()) { + help_data.description = p_text; + } else { + help_data.description = p_text + "\n" + help_data.description; + } -void EditorHelpTooltip::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_POSTINITIALIZE: { - if (!tooltip_text.is_empty()) { - parse_tooltip(tooltip_text); - } - } break; + if (is_inside_tree()) { + _update_labels(); } } -// `p_text` is expected to be something like these: -// - `class|Control||`; -// - `property|Control|size|`; -// - `signal|Control|gui_input|(event: InputEvent)`. -void EditorHelpTooltip::parse_tooltip(const String &p_text) { - tooltip_text = p_text; +void EditorHelpBit::set_content_height_limits(float p_min, float p_max) { + ERR_FAIL_COND(p_min > p_max); + content_min_height = p_min; + content_max_height = p_max; - PackedStringArray slices = p_text.split("|", true, 3); - ERR_FAIL_COND_MSG(slices.size() < 4, "Invalid tooltip formatting. The expect string should be formatted as 'type|class|property|args'."); + if (is_inside_tree()) { + update_content_height(); + } +} - const String &type = slices[0]; - const String &class_name = slices[1]; - const String &property_name = slices[2]; - const String &property_args = slices[3]; +void EditorHelpBit::update_content_height() { + float content_height = content->get_content_height(); + const Ref<StyleBox> style = content->get_theme_stylebox("normal"); + if (style.is_valid()) { + content_height += style->get_content_margin(SIDE_TOP) + style->get_content_margin(SIDE_BOTTOM); + } + content->set_custom_minimum_size(Size2(content->get_custom_minimum_size().x, CLAMP(content_height, content_min_height, content_max_height))); +} - doc_class_name = class_name; +EditorHelpBit::EditorHelpBit(const String &p_symbol) { + add_theme_constant_override("separation", 0); + + title = memnew(RichTextLabel); + title->set_theme_type_variation("EditorHelpBitTitle"); + title->set_fit_content(true); + title->set_selection_enabled(true); + //title->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip. + title->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); + title->hide(); + add_child(title); + + content_min_height = 48 * EDSCALE; + content_max_height = 360 * EDSCALE; + + content = memnew(RichTextLabel); + content->set_theme_type_variation("EditorHelpBitContent"); + content->set_custom_minimum_size(Size2(512 * EDSCALE, content_min_height)); + content->set_selection_enabled(true); + //content->set_context_menu_enabled(true); // TODO: Fix opening context menu hides tooltip. + content->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); + add_child(content); + + if (!p_symbol.is_empty()) { + parse_symbol(p_symbol); + } +} - String formatted_text; +/// EditorHelpBitTooltip /// - // Exclude internal properties, they are not documented. - if (type == "internal_property") { - formatted_text = "[i]" + TTR("This property can only be set in the Inspector.") + "[/i]"; - set_text(formatted_text); - return; +void EditorHelpBitTooltip::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_WM_MOUSE_ENTER: + timer->stop(); + break; + case NOTIFICATION_WM_MOUSE_EXIT: + timer->start(); + break; } +} - String title; - String description; +// Forwards non-mouse input to the parent viewport. +void EditorHelpBitTooltip::_input_from_window(const Ref<InputEvent> &p_event) { + if (p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) { + hide(); // Will be deleted on its timer. + } else { + const Ref<InputEventMouse> mouse_event = p_event; + if (mouse_event.is_null()) { + get_parent_viewport()->push_input(p_event); + } + } +} + +void EditorHelpBitTooltip::show_tooltip(EditorHelpBit *p_help_bit, Control *p_target) { + ERR_FAIL_NULL(p_help_bit); + EditorHelpBitTooltip *tooltip = memnew(EditorHelpBitTooltip(p_target)); + p_help_bit->connect("request_hide", callable_mp(static_cast<Window *>(tooltip), &Window::hide)); // Will be deleted on its timer. + tooltip->add_child(p_help_bit); + p_target->get_viewport()->add_child(tooltip); + p_help_bit->update_content_height(); + tooltip->popup_under_cursor(); +} - if (type == "class") { - title = class_name; - description = get_class_description(class_name); - formatted_text = TTR("Class:"); +// Copy-paste from `Viewport::_gui_show_tooltip()`. +void EditorHelpBitTooltip::popup_under_cursor() { + Point2 mouse_pos = get_mouse_position(); + Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset"); + Rect2 r(mouse_pos + tooltip_offset, get_contents_minimum_size()); + r.size = r.size.min(get_max_size()); + + Window *window = get_parent_visible_window(); + Rect2i vr; + if (is_embedded()) { + vr = get_embedder()->get_visible_rect(); } else { - title = property_name; + vr = window->get_usable_parent_rect(); + } - if (type == "property") { - description = get_property_description(class_name, property_name); - if (property_name.begins_with("metadata/")) { - formatted_text = TTR("Metadata:"); - } else { - formatted_text = TTR("Property:"); - } - } else if (type == "method") { - description = get_method_description(class_name, property_name); - formatted_text = TTR("Method:"); - } else if (type == "signal") { - description = get_signal_description(class_name, property_name); - formatted_text = TTR("Signal:"); - } else if (type == "theme_item") { - description = get_theme_item_description(class_name, property_name); - formatted_text = TTR("Theme Property:"); - } else { - ERR_FAIL_MSG("Invalid tooltip type '" + type + "'. Valid types are 'class', 'property', 'method', 'signal', and 'theme_item'."); + if (r.size.x + r.position.x > vr.size.x + vr.position.x) { + // Place it in the opposite direction. If it fails, just hug the border. + r.position.x = mouse_pos.x - r.size.x - tooltip_offset.x; + + if (r.position.x < vr.position.x) { + r.position.x = vr.position.x + vr.size.x - r.size.x; + } + } else if (r.position.x < vr.position.x) { + r.position.x = vr.position.x; + } + + if (r.size.y + r.position.y > vr.size.y + vr.position.y) { + // Same as above. + r.position.y = mouse_pos.y - r.size.y - tooltip_offset.y; + + if (r.position.y < vr.position.y) { + r.position.y = vr.position.y + vr.size.y - r.size.y; } + } else if (r.position.y < vr.position.y) { + r.position.y = vr.position.y; } - // Metadata special handling replaces "Property:" with "Metadata": above. - formatted_text += " [u][b]" + title.trim_prefix("metadata/") + "[/b][/u]" + property_args.replace("[", "[lb]") + "\n"; - formatted_text += description.is_empty() ? "[i]" + TTR("No description available.") + "[/i]" : description; - set_text(formatted_text); + set_flag(Window::FLAG_NO_FOCUS, true); + popup(r); } -EditorHelpTooltip::EditorHelpTooltip(const String &p_text, const String &p_custom_description) { - tooltip_text = p_text; - custom_description = p_custom_description; +EditorHelpBitTooltip::EditorHelpBitTooltip(Control *p_target) { + set_theme_type_variation("TooltipPanel"); + + timer = memnew(Timer); + timer->set_wait_time(0.2); + timer->connect("timeout", callable_mp(static_cast<Node *>(this), &Node::queue_free)); + add_child(timer); - get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0)); + ERR_FAIL_NULL(p_target); + p_target->connect("mouse_entered", callable_mp(timer, &Timer::stop)); + p_target->connect("mouse_exited", callable_mp(timer, &Timer::start).bind(-1)); } #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) diff --git a/editor/editor_help.h b/editor/editor_help.h index f8686b964a..078b42b439 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -35,9 +35,9 @@ #include "editor/code_editor.h" #include "editor/doc_tools.h" #include "editor/editor_plugin.h" -#include "scene/gui/margin_container.h" #include "scene/gui/menu_button.h" #include "scene/gui/panel_container.h" +#include "scene/gui/popup.h" #include "scene/gui/rich_text_label.h" #include "scene/gui/split_container.h" #include "scene/gui/tab_container.h" @@ -251,53 +251,91 @@ public: ~EditorHelp(); }; -class EditorHelpBit : public MarginContainer { - GDCLASS(EditorHelpBit, MarginContainer); +class EditorHelpBit : public VBoxContainer { + GDCLASS(EditorHelpBit, VBoxContainer); - inline static HashMap<StringName, String> doc_class_cache; - inline static HashMap<StringName, HashMap<StringName, String>> doc_property_cache; - inline static HashMap<StringName, HashMap<StringName, String>> doc_method_cache; - inline static HashMap<StringName, HashMap<StringName, String>> doc_signal_cache; - inline static HashMap<StringName, HashMap<StringName, String>> doc_theme_item_cache; + struct DocType { + String type; + String enumeration; + bool is_bitfield = false; + }; + + struct ArgumentData { + String name; + DocType doc_type; + String default_value; + }; + + struct HelpData { + String description; + String deprecated_message; + String experimental_message; + DocType doc_type; // For method return type. + Vector<ArgumentData> arguments; // For methods and signals. + }; + + inline static HashMap<StringName, HelpData> doc_class_cache; + inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_property_cache; + inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_method_cache; + inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_signal_cache; + inline static HashMap<StringName, HashMap<StringName, HelpData>> doc_theme_item_cache; + + RichTextLabel *title = nullptr; + RichTextLabel *content = nullptr; - RichTextLabel *rich_text = nullptr; + String symbol_class_name; + String symbol_type; + String symbol_visible_type; + String symbol_name; + + HelpData help_data; + + float content_min_height = 0.0; + float content_max_height = 0.0; + + static HelpData _get_class_help_data(const StringName &p_class_name); + static HelpData _get_property_help_data(const StringName &p_class_name, const StringName &p_property_name); + static HelpData _get_method_help_data(const StringName &p_class_name, const StringName &p_method_name); + static HelpData _get_signal_help_data(const StringName &p_class_name, const StringName &p_signal_name); + static HelpData _get_theme_item_help_data(const StringName &p_class_name, const StringName &p_theme_item_name); + + void _add_type_to_title(const DocType &p_doc_type); + void _update_labels(); void _go_to_help(const String &p_what); void _meta_clicked(const String &p_select); - String text; - protected: - String doc_class_name; - String custom_description; - static void _bind_methods(); void _notification(int p_what); public: - String get_class_description(const StringName &p_class_name) const; - String get_property_description(const StringName &p_class_name, const StringName &p_property_name) const; - String get_method_description(const StringName &p_class_name, const StringName &p_method_name) const; - String get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const; - String get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const; + void parse_symbol(const String &p_symbol); + void set_custom_text(const String &p_type, const String &p_name, const String &p_description); + void prepend_description(const String &p_text); - RichTextLabel *get_rich_text() { return rich_text; } - void set_text(const String &p_text); + void set_content_height_limits(float p_min, float p_max); + void update_content_height(); - EditorHelpBit(); + EditorHelpBit(const String &p_symbol = String()); }; -class EditorHelpTooltip : public EditorHelpBit { - GDCLASS(EditorHelpTooltip, EditorHelpBit); +// Standard tooltips do not allow you to hover over them. +// This class is intended as a temporary workaround. +class EditorHelpBitTooltip : public PopupPanel { + GDCLASS(EditorHelpBitTooltip, PopupPanel); - String tooltip_text; + Timer *timer = nullptr; protected: void _notification(int p_what); + virtual void _input_from_window(const Ref<InputEvent> &p_event) override; public: - void parse_tooltip(const String &p_text); + static void show_tooltip(EditorHelpBit *p_help_bit, Control *p_target); + + void popup_under_cursor(); - EditorHelpTooltip(const String &p_text = String(), const String &p_custom_description = String()); + EditorHelpBitTooltip(Control *p_target); }; #if defined(MODULE_GDSCRIPT_ENABLED) || defined(MODULE_MONO_ENABLED) diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index aed1462eb6..50cc89c618 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -44,6 +44,7 @@ #include "editor/plugins/script_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "editor/themes/editor_theme_manager.h" +#include "scene/gui/margin_container.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" #include "scene/property_utils.h" @@ -916,34 +917,35 @@ void EditorProperty::_update_pin_flags() { } Control *EditorProperty::make_custom_tooltip(const String &p_text) const { - EditorHelpBit *tooltip = nullptr; + String custom_warning; + if (object->has_method("_get_property_warning")) { + custom_warning = object->call("_get_property_warning", property); + } - if (has_doc_tooltip) { - String custom_description; + if (has_doc_tooltip || !custom_warning.is_empty()) { + EditorHelpBit *help_bit = memnew(EditorHelpBit); - const EditorInspector *inspector = get_parent_inspector(); - if (inspector) { - custom_description = inspector->get_custom_property_description(p_text); - } - tooltip = memnew(EditorHelpTooltip(p_text, custom_description)); - } + if (has_doc_tooltip) { + help_bit->parse_symbol(p_text); - if (object->has_method("_get_property_warning")) { - String warn = object->call("_get_property_warning", property); - if (!warn.is_empty()) { - String prev_text; - if (tooltip == nullptr) { - tooltip = memnew(EditorHelpBit()); - tooltip->set_text(p_text); - tooltip->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0)); - } else { - prev_text = tooltip->get_rich_text()->get_text() + "\n"; + const EditorInspector *inspector = get_parent_inspector(); + if (inspector) { + const String custom_description = inspector->get_custom_property_description(p_text); + if (!custom_description.is_empty()) { + help_bit->prepend_description(custom_description); + } } - tooltip->set_text(prev_text + "[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + warn + "[/color][/b]"); } + + if (!custom_warning.is_empty()) { + help_bit->prepend_description("[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + custom_warning + "[/color][/b]"); + } + + EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<EditorProperty *>(this)); + return memnew(Control); // Make the standard tooltip invisible. } - return tooltip; + return nullptr; } void EditorProperty::menu_option(int p_option) { @@ -1178,7 +1180,14 @@ void EditorInspectorCategory::_notification(int p_what) { } Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const { - return doc_class_name.is_empty() ? nullptr : memnew(EditorHelpTooltip(p_text)); + // If it's not a doc tooltip, fallback to the default one. + if (doc_class_name.is_empty()) { + return nullptr; + } + + EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text)); + EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<EditorInspectorCategory *>(this)); + return memnew(Control); // Make the standard tooltip invisible. } Size2 EditorInspectorCategory::get_minimum_size() const { @@ -2887,8 +2896,8 @@ void EditorInspector::update_tree() { category->doc_class_name = doc_name; if (use_doc_hints) { - // `|` separator used in `EditorHelpTooltip` for formatting. - category->set_tooltip_text("class|" + doc_name + "||"); + // `|` separators used in `EditorHelpBit`. + category->set_tooltip_text("class|" + doc_name + "|"); } } @@ -3368,15 +3377,15 @@ void EditorInspector::update_tree() { ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED); if (use_doc_hints) { - // `|` separator used in `EditorHelpTooltip` for formatting. + // `|` separators used in `EditorHelpBit`. if (theme_item_name.is_empty()) { if (p.usage & PROPERTY_USAGE_INTERNAL) { - ep->set_tooltip_text("internal_property|" + classname + "|" + property_prefix + p.name + "|"); + ep->set_tooltip_text("internal_property|" + classname + "|" + property_prefix + p.name); } else { - ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name + "|"); + ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name); } } else { - ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name + "|"); + ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name); } ep->has_doc_tooltip = true; } diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index eff4f9caa5..cf3bf89e09 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -41,6 +41,7 @@ class ConfirmationDialog; class EditorInspector; class EditorValidationPanel; class LineEdit; +class MarginContainer; class OptionButton; class PanelContainer; class PopupMenu; diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index c59c221e02..da072744b8 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -40,6 +40,7 @@ #include "editor/inspector_dock.h" #include "editor/themes/editor_scale.h" #include "scene/gui/button.h" +#include "scene/gui/margin_container.h" #include "scene/resources/packed_scene.h" bool EditorPropertyArrayObject::_set(const StringName &p_name, const Variant &p_value) { diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index dae0fc52a6..b00f85c93c 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -37,6 +37,7 @@ class Button; class EditorSpinSlider; +class MarginContainer; class EditorPropertyArrayObject : public RefCounted { GDCLASS(EditorPropertyArrayObject, RefCounted); diff --git a/editor/editor_quick_open.cpp b/editor/editor_quick_open.cpp index 6fd1fd687b..f8df0f9fa8 100644 --- a/editor/editor_quick_open.cpp +++ b/editor/editor_quick_open.cpp @@ -91,17 +91,21 @@ void EditorQuickOpen::_build_search_cache(EditorFileSystemDirectory *p_efsd) { } void EditorQuickOpen::_update_search() { - const String search_text = search_box->get_text(); - const bool empty_search = search_text.is_empty(); + const PackedStringArray search_tokens = search_box->get_text().to_lower().replace("/", " ").split(" ", false); + const bool empty_search = search_tokens.is_empty(); // Filter possible candidates. Vector<Entry> entries; for (int i = 0; i < files.size(); i++) { - if (empty_search || search_text.is_subsequence_ofn(files[i])) { - Entry r; - r.path = files[i]; - r.score = empty_search ? 0 : _score_path(search_text, files[i].to_lower()); + Entry r; + r.path = files[i]; + if (empty_search) { entries.push_back(r); + } else { + r.score = _score_search_result(search_tokens, r.path.to_lower()); + if (r.score > 0) { + entries.push_back(r); + } } } @@ -135,23 +139,42 @@ void EditorQuickOpen::_update_search() { } } -float EditorQuickOpen::_score_path(const String &p_search, const String &p_path) { - float score = 0.9f + .1f * (p_search.length() / (float)p_path.length()); +float EditorQuickOpen::_score_search_result(const PackedStringArray &p_search_tokens, const String &p_path) { + float score = 0.0f; + int prev_min_match_idx = -1; - // Exact match. - if (p_search == p_path) { - return 1.2f; - } + for (const String &s : p_search_tokens) { + int min_match_idx = p_path.find(s); + + if (min_match_idx == -1) { + return 0.0f; + } + + float token_score = s.length(); + + int max_match_idx = p_path.rfind(s); + + // Prioritize the actual file name over folder. + if (max_match_idx > p_path.rfind("/")) { + token_score *= 2.0f; + } + + // Prioritize matches at the front of the path token. + if (min_match_idx == 0 || p_path.find("/" + s) != -1) { + token_score += 1.0f; + } + + score += token_score; + + // Prioritize tokens which appear in order. + if (prev_min_match_idx != -1 && max_match_idx > prev_min_match_idx) { + score += 1.0f; + } - // Positive bias for matches close to the beginning of the file name. - String file = p_path.get_file(); - int pos = file.findn(p_search); - if (pos != -1) { - return score * (1.0f - 0.1f * (float(pos) / file.length())); + prev_min_match_idx = min_match_idx; } - // Similarity - return p_path.to_lower().similarity(p_search.to_lower()); + return score; } void EditorQuickOpen::_confirmed() { diff --git a/editor/editor_quick_open.h b/editor/editor_quick_open.h index ec8ce0175e..bbc689040a 100644 --- a/editor/editor_quick_open.h +++ b/editor/editor_quick_open.h @@ -63,7 +63,7 @@ class EditorQuickOpen : public ConfirmationDialog { void _update_search(); void _build_search_cache(EditorFileSystemDirectory *p_efsd); - float _score_path(const String &p_search, const String &p_path); + float _score_search_result(const PackedStringArray &p_search_tokens, const String &p_path); void _confirmed(); virtual void cancel_pressed() override; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 452715a577..ca6be130f9 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -690,6 +690,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::COLOR, PROPERTY_HINT_NONE, "editors/3d_gizmos/gizmo_colors/aabb", Color(0.28, 0.8, 0.82), "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) // If a line is a multiple of this, it uses the primary grid color. // Use a power of 2 value by default as it's more common to use powers of 2 in level design. diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 2ed547a970..b7deb6afa6 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -59,8 +59,6 @@ #include "scene/gui/label.h" #include "scene/gui/line_edit.h" #include "scene/gui/progress_bar.h" -#include "scene/gui/texture_rect.h" -#include "scene/main/window.h" #include "scene/resources/packed_scene.h" #include "servers/display_server.h" @@ -234,15 +232,15 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory Color custom_color = has_custom_color ? folder_colors[assigned_folder_colors[lpath]] : Color(); if (has_custom_color) { - subdirectory_item->set_icon_modulate(0, editor_is_dark_theme ? custom_color : custom_color * 1.75); - subdirectory_item->set_custom_bg_color(0, Color(custom_color, editor_is_dark_theme ? 0.1 : 0.15)); + subdirectory_item->set_icon_modulate(0, editor_is_dark_theme ? custom_color : custom_color * ITEM_COLOR_SCALE); + subdirectory_item->set_custom_bg_color(0, Color(custom_color, editor_is_dark_theme ? ITEM_ALPHA_MIN : ITEM_ALPHA_MAX)); } else { TreeItem *parent = subdirectory_item->get_parent(); if (parent) { Color parent_bg_color = parent->get_custom_bg_color(0); if (parent_bg_color != Color()) { bool parent_has_custom_color = assigned_folder_colors.has(parent->get_metadata(0)); - subdirectory_item->set_custom_bg_color(0, parent_has_custom_color ? parent_bg_color.darkened(0.3) : parent_bg_color); + subdirectory_item->set_custom_bg_color(0, parent_has_custom_color ? parent_bg_color.darkened(ITEM_BG_DARK_SCALE) : parent_bg_color); subdirectory_item->set_icon_modulate(0, parent->get_icon_modulate(0)); } else { subdirectory_item->set_icon_modulate(0, get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog"))); @@ -267,7 +265,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } else { subdirectory_item->set_collapsed(uncollapsed_paths.find(lpath) < 0); } - if (searched_string.length() > 0 && dname.to_lower().find(searched_string) >= 0) { + if (!searched_tokens.is_empty() && _matches_all_search_tokens(dname)) { parent_should_expand = true; } @@ -293,8 +291,8 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } String file_name = p_dir->get_file(i); - if (searched_string.length() > 0) { - if (file_name.to_lower().find(searched_string) < 0) { + if (!searched_tokens.is_empty()) { + if (!_matches_all_search_tokens(file_name)) { // The searched string is not in the file name, we skip it. continue; } else { @@ -328,7 +326,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory file_item->set_icon_max_width(0, icon_size); Color parent_bg_color = subdirectory_item->get_custom_bg_color(0); if (has_custom_color) { - file_item->set_custom_bg_color(0, parent_bg_color.darkened(0.3)); + file_item->set_custom_bg_color(0, parent_bg_color.darkened(ITEM_BG_DARK_SCALE)); } else if (parent_bg_color != Color()) { file_item->set_custom_bg_color(0, parent_bg_color); } @@ -352,7 +350,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory } } - if (searched_string.length() > 0) { + if (!searched_tokens.is_empty()) { if (parent_should_expand) { subdirectory_item->set_collapsed(false); } else if (dname != "res://") { @@ -460,7 +458,7 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo color = Color(1, 1, 1); } - if (searched_string.length() == 0 || text.to_lower().find(searched_string) >= 0) { + if (searched_tokens.is_empty() || _matches_all_search_tokens(text)) { TreeItem *ti = tree->create_item(favorites_item); ti->set_text(0, text); ti->set_icon(0, icon); @@ -857,7 +855,7 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> * for (int i = 0; i < p_path->get_file_count(); i++) { String file = p_path->get_file(i); - if (file.to_lower().contains(searched_string)) { + if (_matches_all_search_tokens(file)) { FileInfo fi; fi.name = file; fi.type = p_path->get_file_type(i); @@ -984,14 +982,14 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { if (favorite == "res://") { text = "/"; icon = folder_icon; - if (searched_string.length() == 0 || text.to_lower().find(searched_string) >= 0) { + if (searched_tokens.is_empty() || _matches_all_search_tokens(text)) { files->add_item(text, icon, true); files->set_item_metadata(-1, favorite); } } else if (favorite.ends_with("/")) { text = favorite.substr(0, favorite.length() - 1).get_file(); icon = folder_icon; - if (searched_string.length() == 0 || text.to_lower().find(searched_string) >= 0) { + if (searched_tokens.is_empty() || _matches_all_search_tokens(text)) { files->add_item(text, icon, true); files->set_item_metadata(-1, favorite); } @@ -1013,7 +1011,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { fi.modified_time = 0; } - if (searched_string.length() == 0 || fi.name.to_lower().find(searched_string) >= 0) { + if (searched_tokens.is_empty() || _matches_all_search_tokens(fi.name)) { file_list.push_back(fi); } } @@ -1036,7 +1034,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { return; } - if (searched_string.length() > 0) { + if (!searched_tokens.is_empty()) { // Display the search results. // Limit the number of results displayed to avoid an infinite loop. _search(EditorFileSystem::get_singleton()->get_filesystem(), &file_list, 10000); @@ -1068,7 +1066,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->set_item_metadata(-1, bd); files->set_item_selectable(-1, false); - files->set_item_icon_modulate(-1, editor_is_dark_theme ? inherited_folder_color : inherited_folder_color * 1.75); + files->set_item_icon_modulate(-1, editor_is_dark_theme ? inherited_folder_color : inherited_folder_color * ITEM_COLOR_SCALE); } bool reversed = file_sort == FILE_SORT_NAME_REVERSE; @@ -1082,7 +1080,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->add_item(dname, folder_icon, true); files->set_item_metadata(-1, dpath); Color this_folder_color = has_custom_color ? folder_colors[assigned_folder_colors[dpath]] : inherited_folder_color; - files->set_item_icon_modulate(-1, editor_is_dark_theme ? this_folder_color : this_folder_color * 1.75); + files->set_item_icon_modulate(-1, editor_is_dark_theme ? this_folder_color : this_folder_color * ITEM_COLOR_SCALE); if (previous_selection.has(dname)) { files->select(files->get_item_count() - 1, false); @@ -1272,7 +1270,7 @@ void FileSystemDock::_file_list_activate_file(int p_idx) { } void FileSystemDock::_preview_invalidated(const String &p_path) { - if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == current_path && searched_string.length() == 0 && file_list_vb->is_visible_in_tree()) { + if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == current_path && searched_tokens.is_empty() && file_list_vb->is_visible_in_tree()) { for (int i = 0; i < files->get_item_count(); i++) { if (files->get_item_metadata(i) == p_path) { // Re-request preview. @@ -1778,8 +1776,19 @@ void FileSystemDock::_folder_removed(const String &p_folder) { current_path = current_path.get_base_dir(); } - if (assigned_folder_colors.has(p_folder)) { - assigned_folder_colors.erase(p_folder); + // Remove assigned folder color for all subfolders. + bool folder_colors_updated = false; + List<Variant> paths; + assigned_folder_colors.get_key_list(&paths); + for (const Variant &E : paths) { + const String &path = E; + // These folder paths are guaranteed to end with a "/". + if (path.begins_with(p_folder)) { + assigned_folder_colors.erase(path); + folder_colors_updated = true; + } + } + if (folder_colors_updated) { _update_folder_colors_setting(); } @@ -2603,12 +2612,13 @@ void FileSystemDock::_resource_created() { } void FileSystemDock::_search_changed(const String &p_text, const Control *p_from) { - if (searched_string.length() == 0) { + if (searched_tokens.is_empty()) { // Register the uncollapsed paths before they change. uncollapsed_paths_before_search = get_uncollapsed_paths(); } - searched_string = p_text.to_lower(); + const String searched_string = p_text.to_lower(); + searched_tokens = searched_string.split(" ", false); if (p_from == tree_search_box) { file_list_search_box->set_text(searched_string); @@ -2619,16 +2629,29 @@ void FileSystemDock::_search_changed(const String &p_text, const Control *p_from bool unfold_path = (p_text.is_empty() && !current_path.is_empty()); switch (display_mode) { case DISPLAY_MODE_TREE_ONLY: { - _update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path); + _update_tree(searched_tokens.is_empty() ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path); } break; case DISPLAY_MODE_HSPLIT: case DISPLAY_MODE_VSPLIT: { _update_file_list(false); - _update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path); + _update_tree(searched_tokens.is_empty() ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path); } break; } } +bool FileSystemDock::_matches_all_search_tokens(const String &p_text) { + if (searched_tokens.is_empty()) { + return false; + } + const String s = p_text.to_lower(); + for (const String &t : searched_tokens) { + if (!s.contains(t)) { + return false; + } + } + return true; +} + void FileSystemDock::_rescan() { _set_scanning_mode(); EditorFileSystem::get_singleton()->scan(); @@ -3086,6 +3109,8 @@ void FileSystemDock::_folder_color_index_pressed(int p_index, PopupMenu *p_menu) _update_tree(get_uncollapsed_paths()); _update_file_list(true); + + emit_signal(SNAME("folder_color_changed")); } void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vector<String> &p_paths, bool p_display_path_dependent_options) { @@ -3354,7 +3379,7 @@ void FileSystemDock::_file_list_item_clicked(int p_item, const Vector2 &p_pos, M // Popup. if (!paths.is_empty()) { file_list_popup->clear(); - _file_and_folders_fill_popup(file_list_popup, paths, searched_string.length() == 0); + _file_and_folders_fill_popup(file_list_popup, paths, searched_tokens.is_empty()); file_list_popup->set_position(files->get_screen_position() + p_pos); file_list_popup->reset_size(); file_list_popup->popup(); @@ -3367,7 +3392,7 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton } // Right click on empty space for file list. - if (searched_string.length() > 0) { + if (!searched_tokens.is_empty()) { return; } @@ -3796,6 +3821,7 @@ void FileSystemDock::_bind_methods() { ADD_SIGNAL(MethodInfo("folder_removed", PropertyInfo(Variant::STRING, "folder"))); ADD_SIGNAL(MethodInfo("files_moved", PropertyInfo(Variant::STRING, "old_file"), PropertyInfo(Variant::STRING, "new_file"))); ADD_SIGNAL(MethodInfo("folder_moved", PropertyInfo(Variant::STRING, "old_folder"), PropertyInfo(Variant::STRING, "new_folder"))); + ADD_SIGNAL(MethodInfo("folder_color_changed")); ADD_SIGNAL(MethodInfo("display_mode_changed")); } @@ -4113,7 +4139,6 @@ FileSystemDock::FileSystemDock() { new_resource_dialog->set_base_type("Resource"); new_resource_dialog->connect("create", callable_mp(this, &FileSystemDock::_resource_created)); - searched_string = String(); uncollapsed_paths_before_search = Vector<String>(); tree_update_id = 0; diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index 8830f31d2d..058886c91a 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -172,7 +172,7 @@ private: LineEdit *file_list_search_box = nullptr; MenuButton *file_list_button_sort = nullptr; - String searched_string; + PackedStringArray searched_tokens; Vector<String> uncollapsed_paths_before_search; TextureRect *search_icon = nullptr; @@ -311,6 +311,7 @@ private: void _split_dragged(int p_offset); void _search_changed(const String &p_text, const Control *p_from); + bool _matches_all_search_tokens(const String &p_text); MenuButton *_create_file_menu_button(); void _file_sort_popup(int p_id); @@ -381,6 +382,11 @@ protected: static void _bind_methods(); public: + static constexpr double ITEM_COLOR_SCALE = 1.75; + static constexpr double ITEM_ALPHA_MIN = 0.1; + static constexpr double ITEM_ALPHA_MAX = 0.15; + static constexpr double ITEM_BG_DARK_SCALE = 0.3; + const HashMap<String, Color> &get_folder_colors() const; Dictionary get_assigned_folder_colors() const; diff --git a/editor/gui/editor_dir_dialog.cpp b/editor/gui/editor_dir_dialog.cpp index 8821a0eeae..9d5464210b 100644 --- a/editor/gui/editor_dir_dialog.cpp +++ b/editor/gui/editor_dir_dialog.cpp @@ -32,27 +32,44 @@ #include "editor/directory_create_dialog.h" #include "editor/editor_file_system.h" +#include "editor/filesystem_dock.h" +#include "editor/themes/editor_theme_manager.h" #include "scene/gui/box_container.h" #include "scene/gui/tree.h" #include "servers/display_server.h" -void EditorDirDialog::_update_dir(TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path) { +void EditorDirDialog::_update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path) { updating = true; const String path = p_dir->get_path(); p_item->set_metadata(0, path); p_item->set_icon(0, tree->get_editor_theme_icon(SNAME("Folder"))); - p_item->set_icon_modulate(0, tree->get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog"))); if (!p_item->get_parent()) { p_item->set_text(0, "res://"); + p_item->set_icon_modulate(0, p_default_folder_color); } else { if (!opened_paths.has(path) && (p_select_path.is_empty() || !p_select_path.begins_with(path))) { p_item->set_collapsed(true); } p_item->set_text(0, p_dir->get_name()); + + if (p_assigned_folder_colors.has(path)) { + const Color &folder_color = p_folder_colors[p_assigned_folder_colors[path]]; + p_item->set_icon_modulate(0, p_is_dark_theme ? folder_color : folder_color * FileSystemDock::ITEM_COLOR_SCALE); + p_item->set_custom_bg_color(0, Color(folder_color, p_is_dark_theme ? FileSystemDock::ITEM_ALPHA_MIN : FileSystemDock::ITEM_ALPHA_MAX)); + } else { + TreeItem *parent_item = p_item->get_parent(); + Color parent_bg_color = parent_item->get_custom_bg_color(0); + if (parent_bg_color != Color()) { + p_item->set_custom_bg_color(0, p_assigned_folder_colors.has(parent_item->get_metadata(0)) ? parent_bg_color.darkened(FileSystemDock::ITEM_BG_DARK_SCALE) : parent_bg_color); + p_item->set_icon_modulate(0, parent_item->get_icon_modulate(0)); + } else { + p_item->set_icon_modulate(0, p_default_folder_color); + } + } } if (path == new_dir_path || !p_item->get_parent()) { @@ -62,7 +79,7 @@ void EditorDirDialog::_update_dir(TreeItem *p_item, EditorFileSystemDirectory *p updating = false; for (int i = 0; i < p_dir->get_subdir_count(); i++) { TreeItem *ti = tree->create_item(p_item); - _update_dir(ti, p_dir->get_subdir(i)); + _update_dir(p_default_folder_color, p_assigned_folder_colors, p_folder_colors, p_is_dark_theme, ti, p_dir->get_subdir(i)); } } @@ -90,7 +107,7 @@ void EditorDirDialog::reload(const String &p_path) { tree->clear(); TreeItem *root = tree->create_item(); - _update_dir(root, EditorFileSystem::get_singleton()->get_filesystem(), p_path); + _update_dir(tree->get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")), FileSystemDock::get_singleton()->get_assigned_folder_colors(), FileSystemDock::get_singleton()->get_folder_colors(), EditorThemeManager::is_dark_theme(), root, EditorFileSystem::get_singleton()->get_filesystem(), p_path); _item_collapsed(root); new_dir_path.clear(); must_reload = false; @@ -99,6 +116,8 @@ void EditorDirDialog::reload(const String &p_path) { void EditorDirDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + FileSystemDock::get_singleton()->connect("folder_color_changed", callable_mp(this, &EditorDirDialog::reload).bind("")); + EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload).bind("")); reload(); diff --git a/editor/gui/editor_dir_dialog.h b/editor/gui/editor_dir_dialog.h index 40badec212..b10cc8dd9f 100644 --- a/editor/gui/editor_dir_dialog.h +++ b/editor/gui/editor_dir_dialog.h @@ -53,7 +53,7 @@ class EditorDirDialog : public ConfirmationDialog { void _item_collapsed(Object *p_item); void _item_activated(); - void _update_dir(TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String()); + void _update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String()); void _make_dir(); void _make_dir_confirm(const String &p_path); diff --git a/editor/icons/PreviewRotate.svg b/editor/icons/PreviewRotate.svg new file mode 100644 index 0000000000..9e0da46169 --- /dev/null +++ b/editor/icons/PreviewRotate.svg @@ -0,0 +1 @@ +<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#000" stroke-width="2" opacity=".8" stroke-linejoin="round"><circle cx="8" cy="8" r="2"/><path d="M8 1a7 7 0 00-4.982 11.998H1.911v2h4a1 1 0 00.97-1.242l-1-4-1.94.486.28 1.121a5 5 0 117.223.168l1.416 1.416A7 7 0 008 1z"/></g><g fill="#f9f9f9"><circle cx="8" cy="8" r="2"/><path d="M8 1a7 7 0 00-4.982 11.998H1.911v2h4a1 1 0 00.97-1.242l-1-4-1.94.486.28 1.121a5 5 0 117.223.168l1.416 1.416A7 7 0 008 1z"/></g></svg> diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 620ebce44b..325525be1b 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -1117,6 +1117,20 @@ void SceneImportSettingsDialog::_cleanup() { set_process(false); } +void SceneImportSettingsDialog::_on_light_1_switch_pressed() { + light1->set_visible(light_1_switch->is_pressed()); +} + +void SceneImportSettingsDialog::_on_light_2_switch_pressed() { + light2->set_visible(light_2_switch->is_pressed()); +} + +void SceneImportSettingsDialog::_on_light_rotate_switch_pressed() { + bool light_top_level = !light_rotate_switch->is_pressed(); + light1->set_as_top_level_keep_local(light_top_level); + light2->set_as_top_level_keep_local(light_top_level); +} + void SceneImportSettingsDialog::_viewport_input(const Ref<InputEvent> &p_input) { float *rot_x = &cam_rot_x; float *rot_y = &cam_rot_y; @@ -1232,6 +1246,13 @@ void SceneImportSettingsDialog::_re_import() { EditorFileSystem::get_singleton()->reimport_file_with_custom_parameters(base_path, editing_animation ? "animation_library" : "scene", main_settings); } +void SceneImportSettingsDialog::_update_theme_item_cache() { + ConfirmationDialog::_update_theme_item_cache(); + theme_cache.light_1_icon = get_editor_theme_icon(SNAME("MaterialPreviewLight1")); + theme_cache.light_2_icon = get_editor_theme_icon(SNAME("MaterialPreviewLight2")); + theme_cache.rotate_icon = get_editor_theme_icon(SNAME("PreviewRotate")); +} + void SceneImportSettingsDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { @@ -1251,6 +1272,10 @@ void SceneImportSettingsDialog::_notification(int p_what) { animation_play_button->set_icon(get_editor_theme_icon(SNAME("MainPlay"))); } animation_stop_button->set_icon(get_editor_theme_icon(SNAME("Stop"))); + + light_1_switch->set_icon(theme_cache.light_1_icon); + light_2_switch->set_icon(theme_cache.light_2_icon); + light_rotate_switch->set_icon(theme_cache.rotate_icon); } break; case NOTIFICATION_PROCESS: { @@ -1644,6 +1669,40 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { base_viewport->set_use_own_world_3d(true); + HBoxContainer *viewport_hbox = memnew(HBoxContainer); + vp_container->add_child(viewport_hbox); + viewport_hbox->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 2); + + viewport_hbox->add_spacer(); + + VBoxContainer *vb_light = memnew(VBoxContainer); + vb_light->set_v_size_flags(Control::SIZE_EXPAND_FILL); + viewport_hbox->add_child(vb_light); + + light_rotate_switch = memnew(Button); + light_rotate_switch->set_theme_type_variation("PreviewLightButton"); + light_rotate_switch->set_toggle_mode(true); + light_rotate_switch->set_pressed(true); + light_rotate_switch->set_tooltip_text(TTR("Rotate Lights With Model")); + light_rotate_switch->connect("pressed", callable_mp(this, &SceneImportSettingsDialog::_on_light_rotate_switch_pressed)); + vb_light->add_child(light_rotate_switch); + + light_1_switch = memnew(Button); + light_1_switch->set_theme_type_variation("PreviewLightButton"); + light_1_switch->set_toggle_mode(true); + light_1_switch->set_pressed(true); + light_1_switch->set_tooltip_text(TTR("Primary Light")); + light_1_switch->connect("pressed", callable_mp(this, &SceneImportSettingsDialog::_on_light_1_switch_pressed)); + vb_light->add_child(light_1_switch); + + light_2_switch = memnew(Button); + light_2_switch->set_theme_type_variation("PreviewLightButton"); + light_2_switch->set_toggle_mode(true); + light_2_switch->set_pressed(true); + light_2_switch->set_tooltip_text(TTR("Secondary Light")); + light_2_switch->connect("pressed", callable_mp(this, &SceneImportSettingsDialog::_on_light_2_switch_pressed)); + vb_light->add_child(light_2_switch); + camera = memnew(Camera3D); base_viewport->add_child(camera); camera->make_current(); @@ -1675,10 +1734,15 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { environment->set_sky_custom_fov(50.0); camera->set_environment(environment); - light = memnew(DirectionalLight3D); - light->set_transform(Transform3D().looking_at(Vector3(-1, -2, -0.6), Vector3(0, 1, 0))); - base_viewport->add_child(light); - light->set_shadow(true); + light1 = memnew(DirectionalLight3D); + light1->set_transform(Transform3D(Basis::looking_at(Vector3(-1, -1, -1)))); + light1->set_shadow(true); + camera->add_child(light1); + + light2 = memnew(DirectionalLight3D); + light2->set_transform(Transform3D(Basis::looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1)))); + light2->set_color(Color(0.5f, 0.5f, 0.5f)); + camera->add_child(light2); { Ref<StandardMaterial3D> selection_mat; diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h index 17d6616fc0..c2a5151432 100644 --- a/editor/import/3d/scene_import_settings.h +++ b/editor/import/3d/scene_import_settings.h @@ -85,7 +85,18 @@ class SceneImportSettingsDialog : public ConfirmationDialog { bool first_aabb = false; AABB contents_aabb; - DirectionalLight3D *light = nullptr; + Button *light_1_switch = nullptr; + Button *light_2_switch = nullptr; + Button *light_rotate_switch = nullptr; + + struct ThemeCache { + Ref<Texture2D> light_1_icon; + Ref<Texture2D> light_2_icon; + Ref<Texture2D> rotate_icon; + } theme_cache; + + DirectionalLight3D *light1 = nullptr; + DirectionalLight3D *light2 = nullptr; Ref<ArrayMesh> selection_mesh; MeshInstance3D *node_selected = nullptr; @@ -180,6 +191,9 @@ class SceneImportSettingsDialog : public ConfirmationDialog { void _mesh_tree_selected(); void _scene_tree_selected(); void _cleanup(); + void _on_light_1_switch_pressed(); + void _on_light_2_switch_pressed(); + void _on_light_rotate_switch_pressed(); void _viewport_input(const Ref<InputEvent> &p_input); @@ -222,6 +236,7 @@ class SceneImportSettingsDialog : public ConfirmationDialog { Timer *update_view_timer = nullptr; protected: + virtual void _update_theme_item_cache() override; void _notification(int p_what); public: diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index e2aac6c75d..a667f94a62 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -587,7 +587,7 @@ void InputEventConfigurationDialog::_notification(int p_what) { void InputEventConfigurationDialog::popup_and_configure(const Ref<InputEvent> &p_event, const String &p_current_action_name) { if (p_event.is_valid()) { - _set_event(p_event->duplicate(), p_event); + _set_event(p_event->duplicate(), p_event->duplicate()); } else { // Clear Event _set_event(Ref<InputEvent>(), Ref<InputEvent>()); diff --git a/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.cpp new file mode 100644 index 0000000000..16be707d08 --- /dev/null +++ b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.cpp @@ -0,0 +1,79 @@ +/**************************************************************************/ +/* geometry_instance_3d_gizmo_plugin.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "geometry_instance_3d_gizmo_plugin.h" + +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/plugins/node_3d_editor_plugin.h" +#include "scene/3d/visual_instance_3d.h" + +GeometryInstance3DGizmoPlugin::GeometryInstance3DGizmoPlugin() { +} + +bool GeometryInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<GeometryInstance3D>(p_spatial) != nullptr; +} + +String GeometryInstance3DGizmoPlugin::get_gizmo_name() const { + return "MeshInstance3DCustomAABB"; +} + +int GeometryInstance3DGizmoPlugin::get_priority() const { + return -1; +} + +void GeometryInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + GeometryInstance3D *geometry = Object::cast_to<GeometryInstance3D>(p_gizmo->get_node_3d()); + + p_gizmo->clear(); + + if (p_gizmo->is_selected()) { + AABB aabb = geometry->get_custom_aabb(); + + Vector<Vector3> lines; + for (int i = 0; i < 12; i++) { + Vector3 a; + Vector3 b; + aabb.get_edge(i, a, b); + + lines.push_back(a); + lines.push_back(b); + } + + Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D); + mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + const Color selection_box_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/aabb"); + mat->set_albedo(selection_box_color); + mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + p_gizmo->add_lines(lines, mat); + } +} diff --git a/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h new file mode 100644 index 0000000000..2482e277ea --- /dev/null +++ b/editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* geometry_instance_3d_gizmo_plugin.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GEOMETRY_INSTANCE_3D_GIZMO_PLUGIN_H +#define GEOMETRY_INSTANCE_3D_GIZMO_PLUGIN_H + +#include "editor/plugins/node_3d_editor_gizmos.h" + +class GeometryInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(GeometryInstance3DGizmoPlugin, EditorNode3DGizmoPlugin); + +public: + virtual bool has_gizmo(Node3D *p_spatial) override; + virtual String get_gizmo_name() const override; + virtual int get_priority() const override; + + virtual void redraw(EditorNode3DGizmo *p_gizmo) override; + + GeometryInstance3DGizmoPlugin(); +}; + +#endif // GEOMETRY_INSTANCE_3D_GIZMO_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index aea3ea9700..be14132185 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -53,6 +53,7 @@ #include "editor/plugins/gizmos/cpu_particles_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/decal_gizmo_plugin.h" #include "editor/plugins/gizmos/fog_volume_gizmo_plugin.h" +#include "editor/plugins/gizmos/geometry_instance_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/gpu_particles_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/gpu_particles_collision_3d_gizmo_plugin.h" #include "editor/plugins/gizmos/joint_3d_gizmo_plugin.h" @@ -8096,6 +8097,7 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<SoftBody3DGizmoPlugin>(memnew(SoftBody3DGizmoPlugin))); add_gizmo_plugin(Ref<SpriteBase3DGizmoPlugin>(memnew(SpriteBase3DGizmoPlugin))); add_gizmo_plugin(Ref<Label3DGizmoPlugin>(memnew(Label3DGizmoPlugin))); + add_gizmo_plugin(Ref<GeometryInstance3DGizmoPlugin>(memnew(GeometryInstance3DGizmoPlugin))); add_gizmo_plugin(Ref<Marker3DGizmoPlugin>(memnew(Marker3DGizmoPlugin))); add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin))); add_gizmo_plugin(Ref<ShapeCast3DGizmoPlugin>(memnew(ShapeCast3DGizmoPlugin))); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index e4e66b38e9..5e67cbc6ce 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -1476,6 +1476,10 @@ void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) { _fetch_sprite_node(); // Fetch node after set frames. } +bool SpriteFramesEditor::is_editing() const { + return frames.is_valid(); +} + Variant SpriteFramesEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { if (read_only) { return false; @@ -2325,7 +2329,7 @@ bool SpriteFramesEditorPlugin::handles(Object *p_object) const { if (animated_sprite_3d && *animated_sprite_3d->get_sprite_frames()) { return true; } - return p_object->is_class("SpriteFrames"); + return !frames_editor->is_editing() && Object::cast_to<SpriteFrames>(p_object); } void SpriteFramesEditorPlugin::make_visible(bool p_visible) { diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index 2d0e43be1e..e9fbaf7dde 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -253,7 +253,6 @@ class SpriteFramesEditor : public HSplitContainer { void _update_show_settings(); void _edit(); - void _regist_scene_undo(EditorUndoRedoManager *undo_redo); void _fetch_sprite_node(); void _remove_sprite_node(); @@ -270,6 +269,8 @@ protected: public: void edit(Ref<SpriteFrames> p_frames); + bool is_editing() const; + SpriteFramesEditor(); }; diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 0db752e771..166ed05748 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -2267,7 +2267,9 @@ ThemeTypeDialog::ThemeTypeDialog() { /////////////////////// Control *ThemeItemLabel::make_custom_tooltip(const String &p_text) const { - return memnew(EditorHelpTooltip(p_text)); + EditorHelpBit *help_bit = memnew(EditorHelpBit(p_text)); + EditorHelpBitTooltip::show_tooltip(help_bit, const_cast<ThemeItemLabel *>(this)); + return memnew(Control); // Make the standard tooltip invisible. } VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) { @@ -2436,8 +2438,8 @@ HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_ item_name->set_h_size_flags(SIZE_EXPAND_FILL); item_name->set_clip_text(true); item_name->set_text(p_item_name); - // `|` separators used in `EditorHelpTooltip` for formatting. - item_name->set_tooltip_text("theme_item|" + edited_type + "|" + p_item_name + "|"); + // `|` separators used in `EditorHelpBit`. + item_name->set_tooltip_text("theme_item|" + edited_type + "|" + p_item_name); item_name->set_mouse_filter(Control::MOUSE_FILTER_STOP); item_name_container->add_child(item_name); diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index ba3446807e..0bc02789aa 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -321,7 +321,7 @@ public: ThemeTypeDialog(); }; -// Custom `Label` needed to use `EditorHelpTooltip` to display theme item documentation. +// Custom `Label` needed to use `EditorHelpBit` to display theme item documentation. class ThemeItemLabel : public Label { virtual Control *make_custom_tooltip(const String &p_text) const; }; diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index a16767d916..70cef0e345 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1571,6 +1571,8 @@ ProjectManager::ProjectManager() { // Initialize project list. { + project_list->load_project_list(); + Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM); String default_project_path = EDITOR_GET("filesystem/directories/default_project_path"); @@ -1581,13 +1583,10 @@ ProjectManager::ProjectManager() { } } - bool scanned_for_projects = false; // Scanning will update the list automatically. - String autoscan_path = EDITOR_GET("filesystem/directories/autoscan_project_path"); if (!autoscan_path.is_empty()) { if (dir_access->dir_exists(autoscan_path)) { project_list->find_projects(autoscan_path); - scanned_for_projects = true; } else { Error error = dir_access->make_dir_recursive(autoscan_path); if (error != OK) { @@ -1595,10 +1594,8 @@ ProjectManager::ProjectManager() { } } } - - if (!scanned_for_projects) { - project_list->update_project_list(); - } + project_list->update_project_list(); + initialized = true; } // Extend menu bar to window title. diff --git a/editor/project_manager.h b/editor/project_manager.h index d472666d1a..669b5d8b6c 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -141,6 +141,7 @@ class ProjectManager : public Control { void _update_list_placeholder(); ProjectList *project_list = nullptr; + bool initialized = false; LineEdit *search_box = nullptr; Label *loading_label = nullptr; @@ -239,6 +240,7 @@ public: // Project list. + bool is_initialized() const { return initialized; } LineEdit *get_search_box(); // Project tag management. diff --git a/editor/project_manager/project_list.cpp b/editor/project_manager/project_list.cpp index aa93d9414b..d125754dd7 100644 --- a/editor/project_manager/project_list.cpp +++ b/editor/project_manager/project_list.cpp @@ -469,23 +469,19 @@ void ProjectList::update_project_list() { // If you have 150 projects, it may read through 150 files on your disk at once + load 150 icons. // FIXME: Does it really have to be a full, hard reload? Runtime updates should be made much cheaper. - // Clear whole list - for (int i = 0; i < _projects.size(); ++i) { - Item &project = _projects.write[i]; - CRASH_COND(project.control == nullptr); - memdelete(project.control); // Why not queue_free()? - } - _projects.clear(); - _last_clicked = ""; - _selected_project_paths.clear(); + if (ProjectManager::get_singleton()->is_initialized()) { + // Clear whole list + for (int i = 0; i < _projects.size(); ++i) { + Item &project = _projects.write[i]; + CRASH_COND(project.control == nullptr); + memdelete(project.control); // Why not queue_free()? + } - List<String> sections; - _config.load(_config_path); - _config.get_sections(§ions); + _projects.clear(); + _last_clicked = ""; + _selected_project_paths.clear(); - for (const String &path : sections) { - bool favorite = _config.get_value(path, "favorite", false); - _projects.push_back(load_project_data(path, favorite)); + load_project_list(); } // Create controls @@ -590,7 +586,21 @@ void ProjectList::find_projects_multiple(const PackedStringArray &p_paths) { } save_config(); - update_project_list(); + + if (ProjectManager::get_singleton()->is_initialized()) { + update_project_list(); + } +} + +void ProjectList::load_project_list() { + List<String> sections; + _config.load(_config_path); + _config.get_sections(§ions); + + for (const String &path : sections) { + bool favorite = _config.get_value(path, "favorite", false); + _projects.push_back(load_project_data(path, favorite)); + } } void ProjectList::_scan_folder_recursive(const String &p_path, List<String> *r_projects) { diff --git a/editor/project_manager/project_list.h b/editor/project_manager/project_list.h index 86f1f13bd8..981df0f3a0 100644 --- a/editor/project_manager/project_list.h +++ b/editor/project_manager/project_list.h @@ -220,6 +220,7 @@ public: // Project list updates. + void load_project_list(); void update_project_list(); void sort_projects(); int get_project_count() const; diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index ac175d01a6..d7af751f95 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -87,7 +87,7 @@ void PropertySelector::_update_search() { } search_options->clear(); - help_bit->set_text(""); + help_bit->set_custom_text(String(), String(), String()); TreeItem *root = search_options->create_item(); @@ -353,7 +353,7 @@ void PropertySelector::_confirmed() { } void PropertySelector::_item_selected() { - help_bit->set_text(""); + help_bit->set_custom_text(String(), String(), String()); TreeItem *item = search_options->get_selected(); if (!item) { @@ -372,25 +372,21 @@ void PropertySelector::_item_selected() { String text; while (!class_type.is_empty()) { - text = properties ? help_bit->get_property_description(class_type, name) : help_bit->get_method_description(class_type, name); - if (!text.is_empty()) { - break; + if (properties) { + if (ClassDB::has_property(class_type, name, true)) { + help_bit->parse_symbol("property|" + class_type + "|" + name); + break; + } + } else { + if (ClassDB::has_method(class_type, name, true)) { + help_bit->parse_symbol("method|" + class_type + "|" + name); + break; + } } // It may be from a parent class, keep looking. class_type = ClassDB::get_parent_class(class_type); } - - if (!text.is_empty()) { - // Display both property name and description, since the help bit may be displayed - // far away from the location (especially if the dialog was resized to be taller). - help_bit->set_text(vformat("[b]%s[/b]: %s", name, text)); - help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 1)); - } else { - // Use nested `vformat()` as translators shouldn't interfere with BBCode tags. - help_bit->set_text(vformat(TTR("No description available for %s."), vformat("[b]%s[/b]", name))); - help_bit->get_rich_text()->set_self_modulate(Color(1, 1, 1, 0.5)); - } } void PropertySelector::_hide_requested() { @@ -569,8 +565,7 @@ PropertySelector::PropertySelector() { search_options->set_hide_folding(true); help_bit = memnew(EditorHelpBit); - vbc->add_margin_child(TTR("Description:"), help_bit); - help_bit->get_rich_text()->set_fit_content(false); - help_bit->get_rich_text()->set_custom_minimum_size(Size2(help_bit->get_rich_text()->get_minimum_size().x, 135 * EDSCALE)); + help_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE); help_bit->connect("request_hide", callable_mp(this, &PropertySelector::_hide_requested)); + vbc->add_margin_child(TTR("Description:"), help_bit); } diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index 734fa415a3..6151cb4b74 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -356,20 +356,25 @@ EditorThemeManager::ThemeConfiguration EditorThemeManager::_create_theme_config( if (config.spacing_preset != "Custom") { int preset_base_spacing = 0; int preset_extra_spacing = 0; + Size2 preset_dialogs_buttons_min_size; if (config.spacing_preset == "Compact") { preset_base_spacing = 0; preset_extra_spacing = 4; + preset_dialogs_buttons_min_size = Size2(90, 26); } else if (config.spacing_preset == "Spacious") { preset_base_spacing = 6; preset_extra_spacing = 2; + preset_dialogs_buttons_min_size = Size2(112, 36); } else { // Default preset_base_spacing = 4; preset_extra_spacing = 0; + preset_dialogs_buttons_min_size = Size2(105, 34); } config.base_spacing = preset_base_spacing; config.extra_spacing = preset_extra_spacing; + config.dialogs_buttons_min_size = preset_dialogs_buttons_min_size; EditorSettings::get_singleton()->set_initial_value("interface/theme/base_spacing", config.base_spacing); EditorSettings::get_singleton()->set_initial_value("interface/theme/additional_spacing", config.extra_spacing); @@ -1271,6 +1276,9 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the // AcceptDialog. p_theme->set_stylebox("panel", "AcceptDialog", p_config.dialog_style); p_theme->set_constant("buttons_separation", "AcceptDialog", 8 * EDSCALE); + // Make buttons with short texts such as "OK" easier to click/tap. + p_theme->set_constant("buttons_min_width", "AcceptDialog", p_config.dialogs_buttons_min_size.x * EDSCALE); + p_theme->set_constant("buttons_min_height", "AcceptDialog", p_config.dialogs_buttons_min_size.y * EDSCALE); // FileDialog. p_theme->set_icon("folder", "FileDialog", p_theme->get_icon(SNAME("Folder"), EditorStringName(EditorIcons))); @@ -2158,6 +2166,28 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme p_theme->set_constant("text_highlight_v_padding", "EditorHelp", 2 * EDSCALE); } + // EditorHelpBitTitle. + { + Ref<StyleBoxFlat> style = p_config.tree_panel_style->duplicate(); + style->set_bg_color(p_config.dark_theme ? style->get_bg_color().lightened(0.04) : style->get_bg_color().darkened(0.04)); + style->set_border_color(p_config.dark_theme ? style->get_border_color().lightened(0.04) : style->get_border_color().darkened(0.04)); + style->set_corner_radius(CORNER_BOTTOM_LEFT, 0); + style->set_corner_radius(CORNER_BOTTOM_RIGHT, 0); + + p_theme->set_type_variation("EditorHelpBitTitle", "RichTextLabel"); + p_theme->set_stylebox("normal", "EditorHelpBitTitle", style); + } + + // EditorHelpBitContent. + { + Ref<StyleBoxFlat> style = p_config.tree_panel_style->duplicate(); + style->set_corner_radius(CORNER_TOP_LEFT, 0); + style->set_corner_radius(CORNER_TOP_RIGHT, 0); + + p_theme->set_type_variation("EditorHelpBitContent", "RichTextLabel"); + p_theme->set_stylebox("normal", "EditorHelpBitContent", style); + } + // Asset Library. p_theme->set_stylebox("bg", "AssetLib", p_config.base_empty_style); p_theme->set_stylebox("panel", "AssetLib", p_config.content_panel_style); diff --git a/editor/themes/editor_theme_manager.h b/editor/themes/editor_theme_manager.h index 3eb1dd5ffd..5e7bd00083 100644 --- a/editor/themes/editor_theme_manager.h +++ b/editor/themes/editor_theme_manager.h @@ -62,6 +62,7 @@ class EditorThemeManager { int base_spacing = 4; int extra_spacing = 0; + Size2 dialogs_buttons_min_size = Size2(105, 34); int border_width = 0; int corner_radius = 3; diff --git a/main/main.cpp b/main/main.cpp index ed74094c9e..801e8934b0 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -842,21 +842,26 @@ void Main::test_cleanup() { #endif int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) { -#ifdef TESTS_ENABLED for (int x = 0; x < argc; x++) { if ((strncmp(argv[x], "--test", 6) == 0) && (strlen(argv[x]) == 6)) { tests_need_run = true; +#ifdef TESTS_ENABLED // TODO: need to come up with different test contexts. // Not every test requires high-level functionality like `ClassDB`. test_setup(); int status = test_main(argc, argv); test_cleanup(); return status; +#else + ERR_PRINT( + "`--test` was specified on the command line, but this Godot binary was compiled without support for unit tests. Aborting.\n" + "To be able to run unit tests, use the `tests=yes` SCons option when compiling Godot.\n"); + return EXIT_FAILURE; +#endif } } -#endif tests_need_run = false; - return 0; + return EXIT_SUCCESS; } /* Engine initialization diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index 6471c5a142..43349ae6ef 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -256,17 +256,14 @@ Compatibility methods registered. GH-88014 -------- -Validate extension JSON: API was removed: classes/VisualShaderNodeComment - -Removed VisualShaderNodeComment, which is replaced by VisualShaderNodeFrame. +Validate extension JSON: API was removed: classes/VisualShaderNodeComment/methods/get_title +Validate extension JSON: API was removed: classes/VisualShaderNodeComment/methods/set_title +Validate extension JSON: API was removed: classes/VisualShaderNodeComment/properties/title GH-87888 -------- Validate extension JSON: API was removed: classes/Skeleton3D/properties/animate_physical_bones -Validate extension JSON: API was removed: classes/SkeletonIK3D/methods/get_interpolation -Validate extension JSON: API was removed: classes/SkeletonIK3D/methods/set_interpolation -Validate extension JSON: API was removed: classes/SkeletonIK3D/properties/interpolation These base class is changed to SkeletonModifier3D which is processed by Skeleton3D with the assumption that it is Skeleton3D's child. diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index bde23289f4..44a929d285 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -1629,6 +1629,9 @@ void FBXDocument::_generate_skeleton_bone_node(Ref<FBXState> p_state, const GLTF active_skeleton = skeleton; current_node = active_skeleton; + if (active_skeleton) { + p_scene_parent = active_skeleton; + } if (requires_extra_node) { current_node = nullptr; @@ -2019,8 +2022,8 @@ Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool GLTFNodeIndex fbx_root = state->root_nodes.write[0]; Node *fbx_root_node = state->get_scene_node(fbx_root); Node *root = fbx_root_node; - if (fbx_root_node && fbx_root_node->get_parent()) { - root = fbx_root_node->get_parent(); + if (root && root->get_owner() && root->get_owner() != root) { + root = root->get_owner(); } ERR_FAIL_NULL_V(root, nullptr); _process_mesh_instances(state, root); diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index cd19887d82..ec75663e97 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -1997,7 +1997,7 @@ void GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode *p_constant #ifdef DEBUG_ENABLED if (p_is_local) { - if (p_constant->usages == 0) { + if (p_constant->usages == 0 && !String(p_constant->identifier->name).begins_with("_")) { parser->push_warning(p_constant, GDScriptWarning::UNUSED_LOCAL_CONSTANT, p_constant->identifier->name); } } diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index d706c1b101..c526d9c0a4 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -250,7 +250,7 @@ static bool _can_use_validate_call(const MethodBind *p_method, const Vector<GDSc return true; } -GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer, const GDScriptCodeGenerator::Address &p_index_addr) { +GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root, bool p_initializer) { if (p_expression->is_constant && !(p_expression->get_datatype().is_meta_type && p_expression->get_datatype().kind == GDScriptParser::DataType::CLASS)) { return codegen.add_constant(p_expression->reduced_value); } @@ -781,9 +781,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code bool named = subscript->is_attribute; StringName name; GDScriptCodeGenerator::Address index; - if (p_index_addr.mode != GDScriptCodeGenerator::Address::NIL) { - index = p_index_addr; - } else if (subscript->is_attribute) { + if (subscript->is_attribute) { if (subscript->base->type == GDScriptParser::Node::SELF && codegen.script) { GDScriptParser::IdentifierNode *identifier = subscript->attribute; HashMap<StringName, GDScript::MemberInfo>::Iterator MI = codegen.script->member_indices.find(identifier->name); diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index 0adbe1ed8e..637d61ca3b 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -149,13 +149,9 @@ class GDScriptCompiler { void _set_error(const String &p_error, const GDScriptParser::Node *p_node); - Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::BinaryOpNode *on, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - Error _create_binary_operator(CodeGen &codegen, const GDScriptParser::ExpressionNode *p_left_operand, const GDScriptParser::ExpressionNode *p_right_operand, Variant::Operator op, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptDataType _gdtype_from_datatype(const GDScriptParser::DataType &p_datatype, GDScript *p_owner, bool p_handle_metatype = true); - GDScriptCodeGenerator::Address _parse_assign_right_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::AssignmentNode *p_assignmentint, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); - GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false, const GDScriptCodeGenerator::Address &p_index_addr = GDScriptCodeGenerator::Address()); + GDScriptCodeGenerator::Address _parse_expression(CodeGen &codegen, Error &r_error, const GDScriptParser::ExpressionNode *p_expression, bool p_root = false, bool p_initializer = false); GDScriptCodeGenerator::Address _parse_match_pattern(CodeGen &codegen, Error &r_error, const GDScriptParser::PatternNode *p_pattern, const GDScriptCodeGenerator::Address &p_value_addr, const GDScriptCodeGenerator::Address &p_type_addr, const GDScriptCodeGenerator::Address &p_previous_test, bool p_is_first, bool p_is_nested); List<GDScriptCodeGenerator::Address> _add_locals_in_block(CodeGen &codegen, const GDScriptParser::SuiteNode *p_block); void _clear_addresses(CodeGen &codegen, const List<GDScriptCodeGenerator::Address> &p_addresses); diff --git a/modules/gdscript/tests/scripts/parser/features/constants.out b/modules/gdscript/tests/scripts/parser/features/constants.out index 7ec33470d3..d73c5eb7cd 100644 --- a/modules/gdscript/tests/scripts/parser/features/constants.out +++ b/modules/gdscript/tests/scripts/parser/features/constants.out @@ -1,33 +1 @@ GDTEST_OK ->> WARNING ->> Line: 2 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_TEST" is declared but never used in the block. If this is intended, prefix it with an underscore: "__TEST". ->> WARNING ->> Line: 3 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_STRING" is declared but never used in the block. If this is intended, prefix it with an underscore: "__STRING". ->> WARNING ->> Line: 4 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_VECTOR" is declared but never used in the block. If this is intended, prefix it with an underscore: "__VECTOR". ->> WARNING ->> Line: 5 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_ARRAY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__ARRAY". ->> WARNING ->> Line: 6 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_DICTIONARY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__DICTIONARY". ->> WARNING ->> Line: 9 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_HELLO" is declared but never used in the block. If this is intended, prefix it with an underscore: "__HELLO". ->> WARNING ->> Line: 10 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INFINITY" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INFINITY". ->> WARNING ->> Line: 11 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_NOT_A_NUMBER" is declared but never used in the block. If this is intended, prefix it with an underscore: "__NOT_A_NUMBER". diff --git a/modules/gdscript/tests/scripts/parser/features/static_typing.out b/modules/gdscript/tests/scripts/parser/features/static_typing.out index 40a8f97416..d73c5eb7cd 100644 --- a/modules/gdscript/tests/scripts/parser/features/static_typing.out +++ b/modules/gdscript/tests/scripts/parser/features/static_typing.out @@ -1,21 +1 @@ GDTEST_OK ->> WARNING ->> Line: 11 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER". ->> WARNING ->> Line: 12 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_TYPED" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_TYPED". ->> WARNING ->> Line: 13 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_TYPED2" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_TYPED2". ->> WARNING ->> Line: 14 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_INFERRED" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_INFERRED". ->> WARNING ->> Line: 15 ->> UNUSED_LOCAL_CONSTANT ->> The local constant "_INTEGER_REDUNDANT_INFERRED2" is declared but never used in the block. If this is intended, prefix it with an underscore: "__INTEGER_REDUNDANT_INFERRED2". diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd new file mode 100644 index 0000000000..3d355197e1 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.gd @@ -0,0 +1,4 @@ +func test(): + const UNUSED = "not used" + + const _UNUSED = "not used, but no warning since the constant name starts with an underscore" diff --git a/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out new file mode 100644 index 0000000000..99ced48433 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/unused_constant.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_LOCAL_CONSTANT +>> The local constant "UNUSED" is declared but never used in the block. If this is intended, prefix it with an underscore: "_UNUSED". diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index f8c35ab6d1..8f0f0d219e 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5685,6 +5685,9 @@ void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> p_state, const GL active_skeleton = skeleton; current_node = active_skeleton; + if (active_skeleton) { + p_scene_parent = active_skeleton; + } if (requires_extra_node) { current_node = nullptr; @@ -7104,6 +7107,9 @@ Node *GLTFDocument::_generate_scene_node_tree(Ref<GLTFState> p_state) { if (p_state->extensions_used.has("GODOT_single_root")) { _generate_scene_node(p_state, 0, nullptr, nullptr); single_root = p_state->scene_nodes[0]; + if (single_root && single_root->get_owner() && single_root->get_owner() != single_root) { + single_root = single_root->get_owner(); + } } else { single_root = memnew(Node3D); for (int32_t root_i = 0; root_i < p_state->root_nodes.size(); root_i++) { diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index b6f5d6ce57..835fb3e59d 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -1908,7 +1908,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d seams_push_constant.slice = uint32_t(i * subslices + k); seams_push_constant.debug = debug; - RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = rd->draw_list_begin(framebuffers[i * subslices + k], RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); rd->draw_list_bind_uniform_set(draw_list, raster_base_uniform, 0); rd->draw_list_bind_uniform_set(draw_list, blendseams_raster_uniform, 1); diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml index 168e0bf077..b9c69075e1 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayer.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayer.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayer" inherits="Node3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayer" inherits="Node3D" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> The parent class of all OpenXR composition layer nodes. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml index 2de1977671..dd8a11e2b9 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerCylinder" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerCylinder" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as an internal slice of a cylinder. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml index f6eba7e228..716ea72854 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerEquirect" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerEquirect" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as an internal slice of a sphere. </brief_description> diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml index fff592bc4f..6632f90ed2 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerQuad.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="OpenXRCompositionLayerQuad" inherits="OpenXRCompositionLayer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> +<class name="OpenXRCompositionLayerQuad" inherits="OpenXRCompositionLayer" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd"> <brief_description> An OpenXR composition layer that is rendered as a quad. </brief_description> diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 84d3a5f7fa..165d4d5a67 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -161,6 +161,10 @@ Vector<int> TileMap::_get_tile_map_data_using_compatibility_format(int p_layer) return tile_data; } +void TileMap::_set_layer_tile_data(int p_layer, const PackedInt32Array &p_data) { + _set_tile_map_data_using_compatibility_format(p_layer, format, p_data); +} + void TileMap::_notification(int p_what) { switch (p_what) { case TileMap::NOTIFICATION_INTERNAL_PHYSICS_PROCESS: { @@ -740,41 +744,23 @@ Rect2 TileMap::_edit_get_rect() const { #endif bool TileMap::_set(const StringName &p_name, const Variant &p_value) { + int index; + const String sname = p_name; + Vector<String> components = String(p_name).split("/", true, 2); - if (p_name == "format") { + if (sname == "format") { if (p_value.get_type() == Variant::INT) { format = (TileMapDataFormat)(p_value.operator int64_t()); // Set format used for loading. return true; } } #ifndef DISABLE_DEPRECATED - else if (p_name == "tile_data") { // Kept for compatibility reasons. - if (p_value.is_array()) { - if (layers.size() == 0) { - TileMapLayer *new_layer = memnew(TileMapLayer); - add_child(new_layer, false, INTERNAL_MODE_FRONT); - new_layer->set_as_tile_map_internal_node(0); - new_layer->set_name("Layer0"); - new_layer->set_tile_set(tile_set); - new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); - layers.push_back(new_layer); - } - _set_tile_map_data_using_compatibility_format(0, format, p_value); - _emit_changed(); - return true; - } - return false; - } else if (p_name == "cell_quadrant_size") { + else if (sname == "cell_quadrant_size") { set_rendering_quadrant_size(p_value); return true; } #endif // DISABLE_DEPRECATED - else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index < 0) { - return false; - } - + else if (property_helper.is_property_valid(sname, &index)) { if (index >= (int)layers.size()) { while (index >= (int)layers.size()) { TileMapLayer *new_layer = memnew(TileMapLayer); @@ -791,172 +777,38 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { update_configuration_warnings(); } - if (components[1] == "name") { - set_layer_name(index, p_value); - return true; - } else if (components[1] == "enabled") { - set_layer_enabled(index, p_value); - return true; - } else if (components[1] == "modulate") { - set_layer_modulate(index, p_value); - return true; - } else if (components[1] == "y_sort_enabled") { - set_layer_y_sort_enabled(index, p_value); - return true; - } else if (components[1] == "y_sort_origin") { - set_layer_y_sort_origin(index, p_value); - return true; - } else if (components[1] == "z_index") { - set_layer_z_index(index, p_value); - return true; - } else if (components[1] == "navigation_enabled") { - set_layer_navigation_enabled(index, p_value); - return true; - } else if (components[1] == "tile_data") { - _set_tile_map_data_using_compatibility_format(index, format, p_value); - _emit_changed(); + if (property_helper.property_set_value(sname, p_value)) { + if (components[1] == "tile_data") { + _emit_changed(); + } return true; - } else { - return false; } } return false; } bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { + const String sname = p_name; + Vector<String> components = String(p_name).split("/", true, 2); if (p_name == "format") { r_ret = TileMapDataFormat::TILE_MAP_DATA_FORMAT_MAX - 1; // When saving, always save highest format. return true; } #ifndef DISABLE_DEPRECATED - else if (p_name == "cell_quadrant_size") { // Kept for compatibility reasons. + else if (sname == "cell_quadrant_size") { // Kept for compatibility reasons. r_ret = get_rendering_quadrant_size(); return true; } #endif - else if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index < 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - r_ret = get_layer_name(index); - return true; - } else if (components[1] == "enabled") { - r_ret = is_layer_enabled(index); - return true; - } else if (components[1] == "modulate") { - r_ret = get_layer_modulate(index); - return true; - } else if (components[1] == "y_sort_enabled") { - r_ret = is_layer_y_sort_enabled(index); - return true; - } else if (components[1] == "y_sort_origin") { - r_ret = get_layer_y_sort_origin(index); - return true; - } else if (components[1] == "z_index") { - r_ret = get_layer_z_index(index); - return true; - } else if (components[1] == "navigation_enabled") { - r_ret = is_layer_navigation_enabled(index); - return true; - } else if (components[1] == "tile_data") { - r_ret = _get_tile_map_data_using_compatibility_format(index); - return true; - } else { - return false; - } + else { + return property_helper.property_get_value(sname, r_ret); } - return false; } void TileMap::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); - p_list->push_back(PropertyInfo(Variant::NIL, "Layers", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - -#define MAKE_LAYER_PROPERTY(m_type, m_name, m_hint) \ - { \ - const String property_name = vformat("layer_%d/" m_name, i); \ - p_list->push_back(PropertyInfo(m_type, property_name, PROPERTY_HINT_NONE, m_hint, (get(property_name) == property_get_revert(property_name)) ? PROPERTY_USAGE_EDITOR : PROPERTY_USAGE_DEFAULT)); \ - } - - for (uint32_t i = 0; i < layers.size(); i++) { - MAKE_LAYER_PROPERTY(Variant::STRING, "name", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "enabled", ""); - MAKE_LAYER_PROPERTY(Variant::COLOR, "modulate", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "y_sort_enabled", ""); - MAKE_LAYER_PROPERTY(Variant::INT, "y_sort_origin", "suffix:px"); - MAKE_LAYER_PROPERTY(Variant::INT, "z_index", ""); - MAKE_LAYER_PROPERTY(Variant::BOOL, "navigation_enabled", ""); - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("layer_%d/tile_data", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - } - -#undef MAKE_LAYER_PROPERTY -} - -bool TileMap::_property_can_revert(const StringName &p_name) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_")) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index <= 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - return layers[index]->get_name() != default_layer->get_name(); - } else if (components[1] == "enabled") { - return layers[index]->is_enabled() != default_layer->is_enabled(); - } else if (components[1] == "modulate") { - return layers[index]->get_modulate() != default_layer->get_modulate(); - } else if (components[1] == "y_sort_enabled") { - return layers[index]->is_y_sort_enabled() != default_layer->is_y_sort_enabled(); - } else if (components[1] == "y_sort_origin") { - return layers[index]->get_y_sort_origin() != default_layer->get_y_sort_origin(); - } else if (components[1] == "z_index") { - return layers[index]->get_z_index() != default_layer->get_z_index(); - } else if (components[1] == "navigation_enabled") { - return layers[index]->is_navigation_enabled() != default_layer->is_navigation_enabled(); - } - } - - return false; -} - -bool TileMap::_property_get_revert(const StringName &p_name, Variant &r_property) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_")) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index <= 0 || index >= (int)layers.size()) { - return false; - } - - if (components[1] == "name") { - r_property = default_layer->get_name(); - return true; - } else if (components[1] == "enabled") { - r_property = default_layer->is_enabled(); - return true; - } else if (components[1] == "modulate") { - r_property = default_layer->get_modulate(); - return true; - } else if (components[1] == "y_sort_enabled") { - r_property = default_layer->is_y_sort_enabled(); - return true; - } else if (components[1] == "y_sort_origin") { - r_property = default_layer->get_y_sort_origin(); - return true; - } else if (components[1] == "z_index") { - r_property = default_layer->get_z_index(); - return true; - } else if (components[1] == "navigation_enabled") { - r_property = default_layer->is_navigation_enabled(); - return true; - } - } - - return false; + property_helper.get_property_list(p_list, layers.size()); } Vector2 TileMap::map_to_local(const Vector2i &p_pos) const { @@ -1214,11 +1066,26 @@ TileMap::TileMap() { new_layer->set_tile_set(tile_set); new_layer->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &TileMap::_emit_changed)); layers.push_back(new_layer); - default_layer = memnew(TileMapLayer); -} -TileMap::~TileMap() { - memdelete(default_layer); + if (!base_property_helper.is_initialized()) { + // Initialize static PropertyListHelper if it wasn't yet. This has to be done here, + // because creating TileMapLayer in a static context is not always safe. + TileMapLayer *defaults = memnew(TileMapLayer); + + base_property_helper.set_prefix("layer_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults->get_name(), &TileMap::set_layer_name, &TileMap::get_layer_name); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "enabled"), defaults->is_enabled(), &TileMap::set_layer_enabled, &TileMap::is_layer_enabled); + base_property_helper.register_property(PropertyInfo(Variant::COLOR, "modulate"), defaults->get_modulate(), &TileMap::set_layer_modulate, &TileMap::get_layer_modulate); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "y_sort_enabled"), defaults->is_y_sort_enabled(), &TileMap::set_layer_y_sort_enabled, &TileMap::is_layer_y_sort_enabled); + base_property_helper.register_property(PropertyInfo(Variant::INT, "y_sort_origin", PROPERTY_HINT_NONE, "suffix:px"), defaults->get_y_sort_origin(), &TileMap::set_layer_y_sort_origin, &TileMap::get_layer_y_sort_origin); + base_property_helper.register_property(PropertyInfo(Variant::INT, "z_index"), defaults->get_z_index(), &TileMap::set_layer_z_index, &TileMap::get_layer_z_index); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "navigation_enabled"), defaults->is_navigation_enabled(), &TileMap::set_layer_navigation_enabled, &TileMap::is_layer_navigation_enabled); + base_property_helper.register_property(PropertyInfo(Variant::PACKED_INT32_ARRAY, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), Vector<int>(), &TileMap::_set_layer_tile_data, &TileMap::_get_tile_map_data_using_compatibility_format); + + memdelete(defaults); + } + + property_helper.setup_for_instance(base_property_helper, this); } #undef TILEMAP_CALL_FOR_LAYER diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 41068ea978..45604bfb8a 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -32,6 +32,7 @@ #define TILE_MAP_H #include "scene/2d/tile_map_layer.h" +#include "scene/property_list_helper.h" #include "scene/resources/2d/tile_set.h" class Control; @@ -73,7 +74,9 @@ private: // Layers. LocalVector<TileMapLayer *> layers; - TileMapLayer *default_layer; // Dummy layer to fetch default values. + + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; // Transforms for collision_animatable. Transform2D last_valid_transform; @@ -86,13 +89,14 @@ private: // Kept for compatibility with TileMap. With TileMapLayers as individual nodes, the format is stored directly in the array. void _set_tile_map_data_using_compatibility_format(int p_layer, TileMapDataFormat p_format, const Vector<int> &p_data); Vector<int> _get_tile_map_data_using_compatibility_format(int p_layer) const; + void _set_layer_tile_data(int p_layer, const PackedInt32Array &p_data); protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - bool _property_can_revert(const StringName &p_name) const; - bool _property_get_revert(const StringName &p_name, Variant &r_property) const; + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _notification(int p_what); static void _bind_methods(); @@ -235,7 +239,6 @@ public: PackedStringArray get_configuration_warnings() const override; TileMap(); - ~TileMap(); }; VARIANT_ENUM_CAST(TileMap::VisibilityMode); diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 86ff6d15dd..cc923b6676 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -397,7 +397,10 @@ int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const Loc const BSPSimplex &s = p_simplices[p_simplex]; for (int i = 0; i < 4; i++) { const Vector3 v = p_points[s.vertices[i]]; - if (p_plane.has_point(v)) { + // The tolerance used here comes from experiments on scenes up to + // 1000x1000x100 meters. If it's any smaller, some simplices will + // appear to self-intersect due to a lack of precision in Plane. + if (p_plane.has_point(v, 1.0 / (1 << 13))) { // Coplanar. } else if (p_plane.is_point_over(v)) { over++; @@ -419,7 +422,8 @@ int LightmapGI::_bsp_get_simplex_side(const Vector<Vector3> &p_points, const Loc //#define DEBUG_BSP int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const LocalVector<Plane> &p_planes, LocalVector<int32_t> &planes_tested, const LocalVector<BSPSimplex> &p_simplices, const LocalVector<int32_t> &p_simplex_indices, LocalVector<BSPNode> &bsp_nodes) { - //if we reach here, it means there is more than one simplex + ERR_FAIL_COND_V(p_simplex_indices.size() < 2, -1); + int32_t node_index = (int32_t)bsp_nodes.size(); bsp_nodes.push_back(BSPNode()); @@ -477,13 +481,14 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc float score = 0; //by default, score is 0 (worst) if (over_count > 0) { - //give score mainly based on ratio (under / over), this means that this plane is splitting simplices a lot, but its balanced - score = float(under_count) / over_count; + // Simplices that are intersected by the plane are moved into both the over + // and under subtrees which makes the entire tree deeper, so the best plane + // will have the least intersections while separating the simplices evenly. + float balance = float(under_count) / over_count; + float separation = float(over_count + under_count) / p_simplex_indices.size(); + score = balance * separation * separation; } - //adjusting priority over least splits, probably not a great idea - //score *= Math::sqrt(float(over_count + under_count) / p_simplex_indices.size()); //also multiply score - if (score > best_plane_score) { best_plane = plane; best_plane_score = score; @@ -491,6 +496,44 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc } } + // We often end up with two (or on rare occasions, three) simplices that are + // either disjoint or share one vertex and don't have a separating plane + // among their faces. The fallback is to loop through new planes created + // with one vertex of the first simplex and two vertices of the second until + // we find a winner. + if (best_plane_score == 0) { + const BSPSimplex &simplex0 = p_simplices[p_simplex_indices[0]]; + const BSPSimplex &simplex1 = p_simplices[p_simplex_indices[1]]; + + for (uint32_t i = 0; i < 4 && !best_plane_score; i++) { + Vector3 v0 = p_points[simplex0.vertices[i]]; + for (uint32_t j = 0; j < 3 && !best_plane_score; j++) { + if (simplex0.vertices[i] == simplex1.vertices[j]) { + break; + } + Vector3 v1 = p_points[simplex1.vertices[j]]; + for (uint32_t k = j + 1; k < 4; k++) { + if (simplex0.vertices[i] == simplex1.vertices[k]) { + break; + } + Vector3 v2 = p_points[simplex1.vertices[k]]; + + Plane plane = Plane(v0, v1, v2); + if (plane == Plane()) { // When v0, v1, and v2 are collinear, they can't form a plane. + continue; + } + int32_t side0 = _bsp_get_simplex_side(p_points, p_simplices, plane, p_simplex_indices[0]); + int32_t side1 = _bsp_get_simplex_side(p_points, p_simplices, plane, p_simplex_indices[1]); + if ((side0 == 1 && side1 == -1) || (side0 == -1 && side1 == 1)) { + best_plane = plane; + best_plane_score = 1.0; + break; + } + } + } + } + } + LocalVector<int32_t> indices_over; LocalVector<int32_t> indices_under; @@ -515,8 +558,6 @@ int32_t LightmapGI::_compute_bsp_tree(const Vector<Vector3> &p_points, const Loc #endif if (best_plane_score < 0.0 || indices_over.size() == p_simplex_indices.size() || indices_under.size() == p_simplex_indices.size()) { - ERR_FAIL_COND_V(p_simplex_indices.size() <= 1, 0); //should not happen, this is a bug - // Failed to separate the tetrahedrons using planes // this means Delaunay broke at some point. // Luckily, because we are using tetrahedrons, we can resort to diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 5c081a0b47..3b788b2fd0 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -293,7 +293,7 @@ Vector3 Node3D::get_global_rotation_degrees() const { void Node3D::set_global_rotation(const Vector3 &p_euler_rad) { ERR_THREAD_GUARD; Transform3D transform = get_global_transform(); - transform.basis = Basis::from_euler(p_euler_rad); + transform.basis = Basis::from_euler(p_euler_rad) * Basis::from_scale(transform.basis.get_scale()); set_global_transform(transform); } @@ -753,6 +753,15 @@ void Node3D::set_as_top_level(bool p_enabled) { data.top_level = p_enabled; } +void Node3D::set_as_top_level_keep_local(bool p_enabled) { + ERR_THREAD_GUARD; + if (data.top_level == p_enabled) { + return; + } + data.top_level = p_enabled; + _propagate_transform_changed(this); +} + bool Node3D::is_set_as_top_level() const { ERR_READ_THREAD_GUARD_V(false); return data.top_level; diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index 7f8c3e6e68..c1667221df 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -227,6 +227,7 @@ public: void clear_gizmos(); void set_as_top_level(bool p_enabled); + void set_as_top_level_keep_local(bool p_enabled); bool is_set_as_top_level() const; void set_disable_scale(bool p_enabled); diff --git a/scene/3d/skeleton_ik_3d.cpp b/scene/3d/skeleton_ik_3d.cpp index 9581ae58d8..78a21ba9e1 100644 --- a/scene/3d/skeleton_ik_3d.cpp +++ b/scene/3d/skeleton_ik_3d.cpp @@ -366,6 +366,12 @@ void SkeletonIK3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_node"), "set_target_node", "get_target_node"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_distance", PROPERTY_HINT_NONE, "suffix:m"), "set_min_distance", "get_min_distance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_iterations"), "set_max_iterations", "get_max_iterations"); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("set_interpolation", "interpolation"), &SkeletonIK3D::_set_interpolation); + ClassDB::bind_method(D_METHOD("get_interpolation"), &SkeletonIK3D::_get_interpolation); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "interpolation", PROPERTY_HINT_RANGE, "0,1,0.001", PROPERTY_USAGE_NONE), "set_interpolation", "get_interpolation"); +#endif } void SkeletonIK3D::_process_modification() { @@ -415,6 +421,16 @@ StringName SkeletonIK3D::get_tip_bone() const { return tip_bone; } +#ifndef DISABLE_DEPRECATED +void SkeletonIK3D::_set_interpolation(real_t p_interpolation) { + set_influence(p_interpolation); +} + +real_t SkeletonIK3D::_get_interpolation() const { + return get_influence(); +} +#endif + void SkeletonIK3D::set_target_transform(const Transform3D &p_target) { target = p_target; reload_goal(); diff --git a/scene/3d/skeleton_ik_3d.h b/scene/3d/skeleton_ik_3d.h index eff018f2cc..5d6020194e 100644 --- a/scene/3d/skeleton_ik_3d.h +++ b/scene/3d/skeleton_ik_3d.h @@ -134,6 +134,11 @@ class SkeletonIK3D : public SkeletonModifier3D { Variant target_node_override_ref = Variant(); FabrikInverseKinematic::Task *task = nullptr; +#ifndef DISABLE_DEPRECATED + void _set_interpolation(real_t p_interpolation); + real_t _get_interpolation() const; +#endif // DISABLE_DEPRECATED + protected: void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 503c39ae3e..89f7ab2391 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -377,6 +377,7 @@ void GeometryInstance3D::set_custom_aabb(AABB p_aabb) { } custom_aabb = p_aabb; RS::get_singleton()->instance_set_custom_aabb(get_instance(), custom_aabb); + update_gizmos(); } AABB GeometryInstance3D::get_custom_aabb() const { diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index b1870eea55..ad3f607661 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -358,7 +358,7 @@ void Button::_notification(int p_what) { if (!xl_text.is_empty()) { text_buf->set_alignment(align_rtl_checked); - float text_buf_width = MAX(1.0f, drawable_size_remained.width); // The space's width filled by the text_buf. + float text_buf_width = Math::ceil(MAX(1.0f, drawable_size_remained.width)); // The space's width filled by the text_buf. text_buf->set_width(text_buf_width); Point2 text_ofs; diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp index c13a2e281a..e481b78715 100644 --- a/scene/gui/dialogs.cpp +++ b/scene/gui/dialogs.cpp @@ -202,7 +202,7 @@ void AcceptDialog::register_text_enter(LineEdit *p_line_edit) { } void AcceptDialog::_update_child_rects() { - Size2 dlg_size = get_size(); + Size2 dlg_size = Vector2(get_size()) / get_content_scale_factor(); float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT); float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM); @@ -210,6 +210,15 @@ void AcceptDialog::_update_child_rects() { bg_panel->set_position(Point2()); bg_panel->set_size(dlg_size); + for (int i = 0; i < buttons_hbox->get_child_count(); i++) { + Button *b = Object::cast_to<Button>(buttons_hbox->get_child(i)); + if (!b) { + continue; + } + + b->set_custom_minimum_size(Size2(theme_cache.buttons_min_width, theme_cache.buttons_min_height)); + } + // Place the buttons from the bottom edge to their minimum required size. Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size(); Size2 buttons_size = Size2(dlg_size.x - h_margins, buttons_minsize.y); @@ -389,6 +398,8 @@ void AcceptDialog::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, AcceptDialog, panel_style, "panel"); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_separation); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_width); + BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_height); } bool AcceptDialog::swap_cancel_ok = false; diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h index 6f9f450778..12b48c903a 100644 --- a/scene/gui/dialogs.h +++ b/scene/gui/dialogs.h @@ -57,6 +57,8 @@ class AcceptDialog : public Window { struct ThemeCache { Ref<StyleBox> panel_style; int buttons_separation = 0; + int buttons_min_width = 0; + int buttons_min_height = 0; } theme_cache; void _custom_action(const String &p_action); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 12b2364ddf..c3a586a1ee 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -1255,52 +1255,6 @@ int FileDialog::get_option_count() const { return options.size(); } -bool FileDialog::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) { - int item_index = components[0].trim_prefix("option_").to_int(); - String property = components[1]; - if (property == "name") { - set_option_name(item_index, p_value); - return true; - } else if (property == "values") { - set_option_values(item_index, p_value); - return true; - } else if (property == "default") { - set_option_default(item_index, p_value); - return true; - } - } - return false; -} - -bool FileDialog::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("option_") && components[0].trim_prefix("option_").is_valid_int()) { - int item_index = components[0].trim_prefix("option_").to_int(); - String property = components[1]; - if (property == "name") { - r_ret = get_option_name(item_index); - return true; - } else if (property == "values") { - r_ret = get_option_values(item_index); - return true; - } else if (property == "default") { - r_ret = get_option_default(item_index); - return true; - } - } - return false; -} - -void FileDialog::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < options.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("option_%d/name", i))); - p_list->push_back(PropertyInfo(Variant::PACKED_STRING_ARRAY, vformat("option_%d/values", i))); - p_list->push_back(PropertyInfo(Variant::INT, vformat("option_%d/default", i))); - } -} - void FileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("_cancel_pressed"), &FileDialog::_cancel_pressed); @@ -1386,6 +1340,13 @@ void FileDialog::_bind_methods() { BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_hover_color, "font_hover_color", "Button"); BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_focus_color, "font_focus_color", "Button"); BIND_THEME_ITEM_EXT(Theme::DATA_TYPE_COLOR, FileDialog, icon_pressed_color, "font_pressed_color", "Button"); + + Option defaults; + + base_property_helper.set_prefix("option_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "name"), defaults.name, &FileDialog::set_option_name, &FileDialog::get_option_name); + base_property_helper.register_property(PropertyInfo(Variant::PACKED_STRING_ARRAY, "values"), defaults.values, &FileDialog::set_option_values, &FileDialog::get_option_values); + base_property_helper.register_property(PropertyInfo(Variant::INT, "default"), defaults.default_idx, &FileDialog::set_option_default, &FileDialog::get_option_default); } void FileDialog::set_show_hidden_files(bool p_show) { @@ -1563,6 +1524,8 @@ FileDialog::FileDialog() { if (register_func) { register_func(this); } + + property_helper.setup_for_instance(base_property_helper, this); } FileDialog::~FileDialog() { diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h index 7caae7e216..4236f0a56b 100644 --- a/scene/gui/file_dialog.h +++ b/scene/gui/file_dialog.h @@ -37,6 +37,7 @@ #include "scene/gui/line_edit.h" #include "scene/gui/option_button.h" #include "scene/gui/tree.h" +#include "scene/property_list_helper.h" class GridContainer; @@ -137,6 +138,10 @@ private: Vector<String> values; int default_idx = 0; }; + + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + Vector<Option> options; Dictionary selected_options; bool options_dirty = false; @@ -187,9 +192,11 @@ private: protected: void _validate_property(PropertyInfo &p_property) const; void _notification(int p_what); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, options.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } static void _bind_methods(); public: diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 89c627a7a8..e83d9c7c1b 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -153,54 +153,25 @@ void MenuButton::_notification(int p_what) { } bool MenuButton::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { + const String sname = p_name; + if (property_helper.is_property_valid(sname)) { bool valid; - popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); + popup->set(sname.trim_prefix("popup/"), p_value, &valid); return valid; } return false; } bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { + const String sname = p_name; + if (property_helper.is_property_valid(sname)) { bool valid; - r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); + r_ret = popup->get(sname.trim_prefix("popup/"), &valid); return valid; } return false; } -void MenuButton::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < popup->get_item_count(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/checkable", i), PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"); - pi.usage &= ~(!popup->is_item_checkable(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/checked", i)); - pi.usage &= ~(!popup->is_item_checked(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); - pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i)); - pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void MenuButton::_bind_methods() { ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup); ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup); @@ -215,6 +186,17 @@ void MenuButton::_bind_methods() { ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_"); ADD_SIGNAL(MethodInfo("about_to_popup")); + + PopupMenu::Item defaults(true); + + base_property_helper.set_prefix("popup/item_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon); + base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"), defaults.checkable_type); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator); } void MenuButton::set_disable_shortcuts(bool p_disabled) { @@ -235,6 +217,8 @@ MenuButton::MenuButton(const String &p_text) : add_child(popup, false, INTERNAL_MODE_FRONT); popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(true)); popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(false)); + + property_helper.setup_for_instance(base_property_helper, this); } MenuButton::~MenuButton() { diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h index eea6b8e877..2bd577ddd0 100644 --- a/scene/gui/menu_button.h +++ b/scene/gui/menu_button.h @@ -33,6 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" +#include "scene/property_list_helper.h" class MenuButton : public Button { GDCLASS(MenuButton, Button); @@ -42,13 +43,18 @@ class MenuButton : public Button { bool disable_shortcuts = false; PopupMenu *popup = nullptr; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + void _popup_visibility_changed(bool p_visible); protected: void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } static void _bind_methods(); virtual void shortcut_input(const Ref<InputEvent> &p_event) override; diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index 0e10652f07..509c6aca99 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -154,23 +154,20 @@ void OptionButton::_notification(int p_what) { } bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { - const String &property = components[2]; - if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { - return false; - } + int index; + const String sname = p_name; + if (property_helper.is_property_valid(sname, &index)) { bool valid; - popup->set(String(p_name).trim_prefix("popup/"), p_value, &valid); + popup->set(sname.trim_prefix("popup/"), p_value, &valid); - int idx = components[1].get_slice("_", 1).to_int(); - if (idx == current) { + if (index == current) { // Force refreshing currently displayed item. current = NONE_SELECTED; - _select(idx, false); + _select(index, false); } + const String property = sname.get_slice("/", 2); if (property == "text" || property == "icon") { _queue_update_size_cache(); } @@ -180,42 +177,6 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { return false; } -bool OptionButton::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0] == "popup") { - const String &property = components[2]; - if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { - return false; - } - - bool valid; - r_ret = popup->get(String(p_name).trim_prefix("popup/"), &valid); - return valid; - } - return false; -} - -void OptionButton::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < popup->get_item_count(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("popup/item_%d/text", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("popup/item_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(popup->get_item_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::INT, vformat("popup/item_%d/id", i), PROPERTY_HINT_RANGE, "0,10,1,or_greater"); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/disabled", i)); - pi.usage &= ~(!popup->is_item_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("popup/item_%d/separator", i)); - pi.usage &= ~(!popup->is_item_separator(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void OptionButton::_focused(int p_which) { emit_signal(SNAME("item_focused"), p_which); } @@ -606,6 +567,15 @@ void OptionButton::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, OptionButton, arrow_icon, "arrow"); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, arrow_margin); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, modulate_arrow); + + PopupMenu::Item defaults(true); + + base_property_helper.set_prefix("popup/item_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &OptionButton::_dummy_setter, &OptionButton::get_item_text); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &OptionButton::_dummy_setter, &OptionButton::get_item_icon); + base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &OptionButton::_dummy_setter, &OptionButton::is_item_disabled); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &OptionButton::_dummy_setter, &OptionButton::is_item_separator); } void OptionButton::set_disable_shortcuts(bool p_disabled) { @@ -625,6 +595,8 @@ OptionButton::OptionButton(const String &p_text) : popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected)); popup->connect("id_focused", callable_mp(this, &OptionButton::_focused)); popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed).bind(false)); + + property_helper.setup_for_instance(base_property_helper, this); } OptionButton::~OptionButton() { diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h index 9c15b295a9..4b5164161a 100644 --- a/scene/gui/option_button.h +++ b/scene/gui/option_button.h @@ -33,6 +33,7 @@ #include "scene/gui/button.h" #include "scene/gui/popup_menu.h" +#include "scene/property_list_helper.h" class OptionButton : public Button { GDCLASS(OptionButton, Button); @@ -64,11 +65,15 @@ class OptionButton : public Button { int modulate_arrow = 0; } theme_cache; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + void _focused(int p_which); void _selected(int p_which); void _select(int p_which, bool p_emit = false); void _select_int(int p_which); void _refresh_size_cache(); + void _dummy_setter() {} // Stub for PropertyListHelper (_set() doesn't use it). virtual void pressed() override; @@ -78,8 +83,10 @@ protected: void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, popup->get_item_count()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _validate_property(PropertyInfo &p_property) const; static void _bind_methods(); diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp index d90d5da2e9..87383283fd 100644 --- a/scene/gui/popup.cpp +++ b/scene/gui/popup.cpp @@ -230,7 +230,8 @@ Size2 PopupPanel::_get_contents_minimum_size() const { void PopupPanel::_update_child_rects() { Vector2 cpos(theme_cache.panel_style->get_offset()); - Vector2 csize(get_size() - theme_cache.panel_style->get_minimum_size()); + Vector2 panel_size = Vector2(get_size()) / get_content_scale_factor(); + Vector2 csize = panel_size - theme_cache.panel_style->get_minimum_size(); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to<Control>(get_child(i)); @@ -244,7 +245,7 @@ void PopupPanel::_update_child_rects() { if (c == panel) { c->set_position(Vector2()); - c->set_size(get_size()); + c->set_size(panel_size); } else { c->set_position(cpos); c->set_size(csize); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index b6dd3ac0b4..260956a775 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -312,18 +312,17 @@ int PopupMenu::_get_items_total_height() const { } int PopupMenu::_get_mouse_over(const Point2 &p_over) const { - if (p_over.x < 0 || p_over.x >= get_size().width || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP)) { + float win_scale = get_content_scale_factor(); + if (p_over.x < 0 || p_over.x >= get_size().width * win_scale || p_over.y < theme_cache.panel_style->get_margin(Side::SIDE_TOP) * win_scale) { return -1; } - Point2 ofs; + Point2 ofs = Point2(0, theme_cache.v_separation * 0.5) * win_scale; for (int i = 0; i < items.size(); i++) { - ofs.y += theme_cache.v_separation; - - ofs.y += _get_item_height(i); - - if (p_over.y - control->get_position().y < ofs.y) { + ofs.y += i > 0 ? (float)theme_cache.v_separation * win_scale : (float)theme_cache.v_separation * win_scale * 0.5; + ofs.y += _get_item_height(i) * win_scale; + if (p_over.y - control->get_position().y * win_scale < ofs.y) { return i; } } @@ -341,15 +340,17 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { Rect2 this_rect(this_pos, get_size()); float scroll_offset = control->get_position().y; + float scaled_ofs_cache = items[p_over]._ofs_cache * get_content_scale_factor(); + float scaled_height_cache = items[p_over]._height_cache * get_content_scale_factor(); submenu_popup->reset_size(); // Shrink the popup size to its contents. Size2 submenu_size = submenu_popup->get_size(); Point2 submenu_pos; if (control->is_layout_rtl()) { - submenu_pos = this_pos + Point2(-submenu_size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2); + submenu_pos = this_pos + Point2(-submenu_size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); } else { - submenu_pos = this_pos + Point2(this_rect.size.width, items[p_over]._ofs_cache + scroll_offset - theme_cache.v_separation / 2); + submenu_pos = this_pos + Point2(this_rect.size.width, scaled_ofs_cache + scroll_offset - theme_cache.v_separation / 2); } // Fix pos if going outside parent rect. @@ -386,8 +387,8 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Set autohide areas. Rect2 safe_area = this_rect; - safe_area.position.y += items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; - safe_area.size.y = items[p_over]._height_cache + theme_cache.v_separation; + safe_area.position.y += scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2; + safe_area.size.y = scaled_height_cache + theme_cache.v_separation; Viewport *vp = submenu_popup->get_embedder(); if (vp) { vp->subwindow_set_popup_safe_rect(submenu_popup, safe_area); @@ -400,11 +401,11 @@ void PopupMenu::_activate_submenu(int p_over, bool p_by_keyboard) { // Autohide area above the submenu item. submenu_pum->clear_autohide_areas(); - submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); + submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, scaled_ofs_cache + scroll_offset + theme_cache.panel_style->get_offset().height - theme_cache.v_separation / 2)); // If there is an area below the submenu item, add an autohide area there. - if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) { - int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; + if (scaled_ofs_cache + scaled_height_cache + scroll_offset <= control->get_size().height) { + int from = scaled_ofs_cache + scaled_height_cache + scroll_offset + theme_cache.v_separation / 2 + theme_cache.panel_style->get_offset().height; submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from)); } } @@ -576,6 +577,7 @@ void PopupMenu::_input_from_window_internal(const Ref<InputEvent> &p_event) { } item_clickable_area.size.width -= scroll_container->get_v_scroll_bar()->get_size().width; } + item_clickable_area.size = item_clickable_area.size * get_content_scale_factor(); Ref<InputEventMouseButton> b = p_event; @@ -2827,6 +2829,14 @@ void PopupMenu::popup(const Rect2i &p_bounds) { } else { moved = Vector2(); popup_time_msec = OS::get_singleton()->get_ticks_msec(); + if (!is_embedded()) { + float win_scale = get_parent_visible_window()->get_content_scale_factor(); + set_content_scale_factor(win_scale); + Size2 minsize = get_contents_minimum_size(); + minsize.height += 0.5 * win_scale; // Ensures enough height at fractional content scales to prevent the v_scroll_bar from showing. + set_min_size(minsize * win_scale); + set_size(Vector2(0, 0)); // Shrinkwraps to min size. + } Popup::popup(p_bounds); } } diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index f7097fffaf..832c1bcc8b 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -97,6 +97,10 @@ class PopupMenu : public Popup { static inline PropertyListHelper base_property_helper; PropertyListHelper property_helper; + // To make Item available. + friend class OptionButton; + friend class MenuButton; + RID global_menu; RID system_menu; NativeMenu::SystemMenus system_menu_id = NativeMenu::INVALID_MENU_ID; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index d20fef8164..2d687eb201 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -1720,58 +1720,6 @@ bool TabBar::get_deselect_enabled() const { return deselect_enabled; } -bool TabBar::_set(const StringName &p_name, const Variant &p_value) { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { - int tab_index = components[0].trim_prefix("tab_").to_int(); - const String &property = components[1]; - if (property == "title") { - set_tab_title(tab_index, p_value); - return true; - } else if (property == "icon") { - set_tab_icon(tab_index, p_value); - return true; - } else if (property == "disabled") { - set_tab_disabled(tab_index, p_value); - return true; - } - } - return false; -} - -bool TabBar::_get(const StringName &p_name, Variant &r_ret) const { - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { - int tab_index = components[0].trim_prefix("tab_").to_int(); - const String &property = components[1]; - if (property == "title") { - r_ret = get_tab_title(tab_index); - return true; - } else if (property == "icon") { - r_ret = get_tab_icon(tab_index); - return true; - } else if (property == "disabled") { - r_ret = is_tab_disabled(tab_index); - return true; - } - } - return false; -} - -void TabBar::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < tabs.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("tab_%d/title", i))); - - PropertyInfo pi = PropertyInfo(Variant::OBJECT, vformat("tab_%d/icon", i), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"); - pi.usage &= ~(get_tab_icon(i).is_null() ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - - pi = PropertyInfo(Variant::BOOL, vformat("tab_%d/disabled", i)); - pi.usage &= ~(!is_tab_disabled(i) ? PROPERTY_USAGE_STORAGE : 0); - p_list->push_back(pi); - } -} - void TabBar::_bind_methods() { ClassDB::bind_method(D_METHOD("set_tab_count", "count"), &TabBar::set_tab_count); ClassDB::bind_method(D_METHOD("get_tab_count"), &TabBar::get_tab_count); @@ -1890,10 +1838,19 @@ void TabBar::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, TabBar, close_icon, "close"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, button_pressed_style, "button_pressed"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, TabBar, button_hl_style, "button_highlight"); + + Tab defaults(true); + + base_property_helper.set_prefix("tab_"); + base_property_helper.register_property(PropertyInfo(Variant::STRING, "title"), defaults.text, &TabBar::set_tab_title, &TabBar::get_tab_title); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &TabBar::set_tab_icon, &TabBar::get_tab_icon); + base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &TabBar::set_tab_disabled, &TabBar::is_tab_disabled); } TabBar::TabBar() { set_size(Size2(get_size().width, get_minimum_size().height)); set_focus_mode(FOCUS_ALL); connect("mouse_exited", callable_mp(this, &TabBar::_on_mouse_exited)); + + property_helper.setup_for_instance(base_property_helper, this); } diff --git a/scene/gui/tab_bar.h b/scene/gui/tab_bar.h index 65a1d5bd4f..6c09e960f1 100644 --- a/scene/gui/tab_bar.h +++ b/scene/gui/tab_bar.h @@ -32,6 +32,7 @@ #define TAB_BAR_H #include "scene/gui/control.h" +#include "scene/property_list_helper.h" #include "scene/resources/text_line.h" class TabBar : public Control { @@ -77,8 +78,13 @@ private: Tab() { text_buf.instantiate(); } + + Tab(bool p_dummy) {} }; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + int offset = 0; int max_drawn_tab = 0; int highlight_arrow = -1; @@ -163,9 +169,11 @@ private: protected: virtual void gui_input(const Ref<InputEvent> &p_event) override; - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, tabs.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } void _notification(int p_what); static void _bind_methods(); diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 1daf86fe0f..9a2ba23ce8 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -2459,7 +2459,6 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2 if (rtl) { button_ofs.x = get_size().width - button_ofs.x - button_texture->get_width(); } - p_item->cells.write[i].buttons.write[j].rect = Rect2i(button_ofs, button_size); button_texture->draw(ci, button_ofs, p_item->cells[i].buttons[j].disabled ? Color(1, 1, 1, 0.5) : p_item->cells[i].buttons[j].color); item_width_with_buttons -= button_size.width + theme_cache.button_margin; } @@ -3995,12 +3994,14 @@ bool Tree::edit_selected(bool p_force_edit) { return false; } + float popup_scale = popup_editor->is_embedded() ? 1.0 : popup_editor->get_parent_visible_window()->get_content_scale_factor(); Rect2 rect; if (select_mode == SELECT_ROW) { rect = s->get_meta("__focus_col_" + itos(selected_col)); } else { rect = s->get_meta("__focus_rect"); } + rect.position *= popup_scale; popup_edited_item = s; popup_edited_item_col = col; @@ -4043,7 +4044,7 @@ bool Tree::edit_selected(bool p_force_edit) { popup_rect.size = rect.size; // Account for icon. - Size2 icon_size = _get_cell_icon_size(c); + Size2 icon_size = _get_cell_icon_size(c) * popup_scale; popup_rect.position.x += icon_size.x; popup_rect.size.x -= icon_size.x; @@ -4070,7 +4071,10 @@ bool Tree::edit_selected(bool p_force_edit) { } popup_editor->set_position(popup_rect.position); - popup_editor->set_size(popup_rect.size); + popup_editor->set_size(popup_rect.size * popup_scale); + if (!popup_editor->is_embedded()) { + popup_editor->set_content_scale_factor(popup_scale); + } popup_editor->popup(); popup_editor->child_controls_changed(); @@ -4086,7 +4090,10 @@ bool Tree::edit_selected(bool p_force_edit) { text_editor->show(); popup_editor->set_position(get_screen_position() + rect.position); - popup_editor->set_size(rect.size); + popup_editor->set_size(rect.size * popup_scale); + if (!popup_editor->is_embedded()) { + popup_editor->set_content_scale_factor(popup_scale); + } popup_editor->popup(); popup_editor->child_controls_changed(); @@ -5400,7 +5407,6 @@ String Tree::get_tooltip(const Point2 &p_pos) const { return Control::get_tooltip(p_pos); } - Point2 button_pos = pos; if (h_scroll->is_visible_in_tree()) { pos.x += h_scroll->get_value(); } @@ -5413,13 +5419,22 @@ String Tree::get_tooltip(const Point2 &p_pos) const { if (it) { const TreeItem::Cell &c = it->cells[col]; + int col_width = get_column_width(col); + + for (int i = 0; i < col; i++) { + pos.x -= get_column_width(i); + } + for (int j = c.buttons.size() - 1; j >= 0; j--) { - if (c.buttons[j].rect.has_point(button_pos)) { + Ref<Texture2D> b = c.buttons[j].texture; + Size2 size = b->get_size() + theme_cache.button_pressed->get_minimum_size(); + if (pos.x > col_width - size.width) { String tooltip = c.buttons[j].tooltip; if (!tooltip.is_empty()) { return tooltip; } } + col_width -= size.width; } String ret; if (it->get_tooltip_text(col) == "") { diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 21696d8216..c7c266a2e7 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -108,7 +108,6 @@ private: Ref<Texture2D> texture; Color color = Color(1, 1, 1, 1); String tooltip; - Rect2i rect; }; Vector<Button> buttons; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 17d7fec230..2d30ea345d 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -1461,6 +1461,8 @@ void Viewport::_gui_show_tooltip() { panel->set_flag(Window::FLAG_NO_FOCUS, true); panel->set_flag(Window::FLAG_POPUP, false); panel->set_flag(Window::FLAG_MOUSE_PASSTHROUGH, true); + // A non-embedded tooltip window will only be transparent if per_pixel_transparency is allowed in the main Viewport. + panel->set_flag(Window::FLAG_TRANSPARENT, true); panel->set_wrap_controls(true); panel->add_child(base_tooltip); panel->gui_parent = this; @@ -1469,17 +1471,25 @@ void Viewport::_gui_show_tooltip() { tooltip_owner->add_child(gui.tooltip_popup); + Window *window = Object::cast_to<Window>(gui.tooltip_popup->get_embedder()); + if (!window) { // Not embedded. + window = gui.tooltip_popup->get_parent_visible_window(); + } + float win_scale = window->content_scale_factor; Point2 tooltip_offset = GLOBAL_GET("display/mouse_cursor/tooltip_position_offset"); + if (!gui.tooltip_popup->is_embedded()) { + tooltip_offset *= win_scale; + } Rect2 r(gui.tooltip_pos + tooltip_offset, gui.tooltip_popup->get_contents_minimum_size()); - r.size = r.size.min(panel->get_max_size()); - - Window *window = gui.tooltip_popup->get_parent_visible_window(); Rect2i vr; if (gui.tooltip_popup->is_embedded()) { vr = gui.tooltip_popup->get_embedder()->get_visible_rect(); } else { + panel->content_scale_factor = win_scale; + r.size *= win_scale; vr = window->get_usable_parent_rect(); } + r.size = r.size.min(panel->get_max_size()); if (r.size.x + r.position.x > vr.size.x + vr.position.x) { // Place it in the opposite direction. If it fails, just hug the border. diff --git a/scene/property_list_helper.cpp b/scene/property_list_helper.cpp index d9a80011b0..b666e4c52d 100644 --- a/scene/property_list_helper.cpp +++ b/scene/property_list_helper.cpp @@ -31,7 +31,7 @@ #include "property_list_helper.h" const PropertyListHelper::Property *PropertyListHelper::_get_property(const String &p_property, int *r_index) const { - const Vector<String> components = p_property.split("/", true, 2); + const Vector<String> components = p_property.rsplit("/", true, 1); if (components.size() < 2 || !components[0].begins_with(prefix)) { return nullptr; } @@ -48,36 +48,73 @@ const PropertyListHelper::Property *PropertyListHelper::_get_property(const Stri } void PropertyListHelper::_call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const { + DEV_ASSERT(p_setter); Variant args[] = { p_index, p_value }; const Variant *argptrs[] = { &args[0], &args[1] }; Callable::CallError ce; p_setter->call(object, argptrs, 2, ce); } -Variant PropertyListHelper::_call_getter(const MethodBind *p_getter, int p_index) const { +Variant PropertyListHelper::_call_getter(const Property *p_property, int p_index) const { + if (!p_property->getter) { + return object->get(prefix + itos(p_index) + "/" + p_property->info.name); + } + Callable::CallError ce; Variant args[] = { p_index }; const Variant *argptrs[] = { &args[0] }; - return p_getter->call(object, argptrs, 1, ce); + return p_property->getter->call(object, argptrs, 1, ce); } void PropertyListHelper::set_prefix(const String &p_prefix) { prefix = p_prefix; } +void PropertyListHelper::register_property(const PropertyInfo &p_info, const Variant &p_default) { + Property property; + property.info = p_info; + property.default_value = p_default; + + property_list[p_info.name] = property; +} + +bool PropertyListHelper::is_initialized() const { + return !property_list.is_empty(); +} + void PropertyListHelper::setup_for_instance(const PropertyListHelper &p_base, Object *p_object) { prefix = p_base.prefix; property_list = p_base.property_list; object = p_object; } +bool PropertyListHelper::is_property_valid(const String &p_property, int *r_index) const { + const Vector<String> components = p_property.rsplit("/", true, 1); + if (components.size() < 2 || !components[0].begins_with(prefix)) { + return false; + } + + { + const String index_string = components[0].trim_prefix(prefix); + if (!index_string.is_valid_int()) { + return false; + } + + if (r_index) { + *r_index = index_string.to_int(); + } + } + + return property_list.has(components[1]); +} + void PropertyListHelper::get_property_list(List<PropertyInfo> *p_list, int p_count) const { for (int i = 0; i < p_count; i++) { for (const KeyValue<String, Property> &E : property_list) { const Property &property = E.value; PropertyInfo info = property.info; - if (_call_getter(property.getter, i) == property.default_value) { + if (_call_getter(&property, i) == property.default_value) { info.usage &= (~PROPERTY_USAGE_STORAGE); } @@ -92,7 +129,7 @@ bool PropertyListHelper::property_get_value(const String &p_property, Variant &r const Property *property = _get_property(p_property, &index); if (property) { - r_ret = _call_getter(property->getter, index); + r_ret = _call_getter(property, index); return true; } return false; @@ -110,8 +147,7 @@ bool PropertyListHelper::property_set_value(const String &p_property, const Vari } bool PropertyListHelper::property_can_revert(const String &p_property) const { - int index; - return _get_property(p_property, &index) != nullptr; + return is_property_valid(p_property); } bool PropertyListHelper::property_get_revert(const String &p_property, Variant &r_value) const { @@ -129,8 +165,10 @@ PropertyListHelper::~PropertyListHelper() { // No object = it's the main helper. Do a cleanup. if (!object) { for (const KeyValue<String, Property> &E : property_list) { - memdelete(E.value.setter); - memdelete(E.value.getter); + if (E.value.setter) { + memdelete(E.value.setter); + memdelete(E.value.getter); + } } } } diff --git a/scene/property_list_helper.h b/scene/property_list_helper.h index 6c1ad21a05..eac6b03d47 100644 --- a/scene/property_list_helper.h +++ b/scene/property_list_helper.h @@ -48,10 +48,12 @@ class PropertyListHelper { const Property *_get_property(const String &p_property, int *r_index) const; void _call_setter(const MethodBind *p_setter, int p_index, const Variant &p_value) const; - Variant _call_getter(const MethodBind *p_getter, int p_index) const; + Variant _call_getter(const Property *p_property, int p_index) const; public: void set_prefix(const String &p_prefix); + // Register property without setter/getter. Only use when you don't need PropertyListHelper for _set/_get logic. + void register_property(const PropertyInfo &p_info, const Variant &p_default); template <typename S, typename G> void register_property(const PropertyInfo &p_info, const Variant &p_default, S p_setter, G p_getter) { @@ -64,7 +66,9 @@ public: property_list[p_info.name] = property; } + bool is_initialized() const; void setup_for_instance(const PropertyListHelper &p_base, Object *p_object); + bool is_property_valid(const String &p_property, int *r_index = nullptr) const; void get_property_list(List<PropertyInfo> *p_list, int p_count) const; bool property_get_value(const String &p_property, Variant &r_ret) const; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 1c8833494d..d13f338444 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -657,6 +657,9 @@ void register_scene_types() { GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeConstant); GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVectorBase); GDREGISTER_CLASS(VisualShaderNodeFrame); +#ifndef DISABLE_DEPRECATED + GDREGISTER_CLASS(VisualShaderNodeComment); // Deprecated, just for compatibility. +#endif GDREGISTER_CLASS(VisualShaderNodeFloatConstant); GDREGISTER_CLASS(VisualShaderNodeIntConstant); GDREGISTER_CLASS(VisualShaderNodeUIntConstant); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 9ac899ad78..7e80d0be3c 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -4206,7 +4206,7 @@ VisualShaderNodeResizableBase::VisualShaderNodeResizableBase() { set_allow_v_resize(true); } -////////////// Comment +////////////// Frame String VisualShaderNodeFrame::get_caption() const { return title; @@ -4323,6 +4323,25 @@ void VisualShaderNodeFrame::_bind_methods() { VisualShaderNodeFrame::VisualShaderNodeFrame() { } +////////////// Comment (Deprecated) + +#ifndef DISABLE_DEPRECATED +void VisualShaderNodeComment::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_description", "description"), &VisualShaderNodeComment::set_description); + ClassDB::bind_method(D_METHOD("get_description"), &VisualShaderNodeComment::get_description); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_description", "get_description"); +} + +void VisualShaderNodeComment::set_description(const String &p_description) { + description = p_description; +} + +String VisualShaderNodeComment::get_description() const { + return description; +} +#endif + ////////////// GroupBase void VisualShaderNodeGroupBase::set_inputs(const String &p_inputs) { diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 3ef6dcd4f9..d7270f3ac6 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -767,6 +767,28 @@ public: VisualShaderNodeFrame(); }; +#ifndef DISABLE_DEPRECATED +// Deprecated, for compatibility only. +class VisualShaderNodeComment : public VisualShaderNodeFrame { + GDCLASS(VisualShaderNodeComment, VisualShaderNodeFrame); + + String description; + +protected: + static void _bind_methods(); + +public: + virtual String get_caption() const override { return "Comment(Deprecated)"; } + + virtual Category get_category() const override { return CATEGORY_NONE; } + + void set_description(const String &p_description); + String get_description() const; + + VisualShaderNodeComment() {} +}; +#endif + class VisualShaderNodeGroupBase : public VisualShaderNodeResizableBase { GDCLASS(VisualShaderNodeGroupBase, VisualShaderNodeResizableBase); diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index 3e3a7d2381..b5333d91c6 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -676,63 +676,6 @@ bool AudioStreamRandomizer::is_monophonic() const { return false; } -bool AudioStreamRandomizer::_get(const StringName &p_name, Variant &r_ret) const { - if (AudioStream::_get(p_name, r_ret)) { - return true; - } - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("stream_") && components[0].trim_prefix("stream_").is_valid_int()) { - int index = components[0].trim_prefix("stream_").to_int(); - if (index < 0 || index >= (int)audio_stream_pool.size()) { - return false; - } - - if (components[1] == "stream") { - r_ret = get_stream(index); - return true; - } else if (components[1] == "weight") { - r_ret = get_stream_probability_weight(index); - return true; - } else { - return false; - } - } - return false; -} - -bool AudioStreamRandomizer::_set(const StringName &p_name, const Variant &p_value) { - if (AudioStream::_set(p_name, p_value)) { - return true; - } - Vector<String> components = String(p_name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("stream_") && components[0].trim_prefix("stream_").is_valid_int()) { - int index = components[0].trim_prefix("stream_").to_int(); - if (index < 0 || index >= (int)audio_stream_pool.size()) { - return false; - } - - if (components[1] == "stream") { - set_stream(index, p_value); - return true; - } else if (components[1] == "weight") { - set_stream_probability_weight(index, p_value); - return true; - } else { - return false; - } - } - return false; -} - -void AudioStreamRandomizer::_get_property_list(List<PropertyInfo> *p_list) const { - AudioStream::_get_property_list(p_list); // Define the trivial scalar properties. - p_list->push_back(PropertyInfo(Variant::NIL, "Streams", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < audio_stream_pool.size(); i++) { - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("stream_%d/stream", i), PROPERTY_HINT_RESOURCE_TYPE, "AudioStream")); - p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("stream_%d/weight", i), PROPERTY_HINT_RANGE, "0,100,0.001,or_greater")); - } -} - void AudioStreamRandomizer::_bind_methods() { ClassDB::bind_method(D_METHOD("add_stream", "index", "stream", "weight"), &AudioStreamRandomizer::add_stream, DEFVAL(1.0)); ClassDB::bind_method(D_METHOD("move_stream", "index_from", "index_to"), &AudioStreamRandomizer::move_stream); @@ -764,9 +707,17 @@ void AudioStreamRandomizer::_bind_methods() { BIND_ENUM_CONSTANT(PLAYBACK_RANDOM_NO_REPEATS); BIND_ENUM_CONSTANT(PLAYBACK_RANDOM); BIND_ENUM_CONSTANT(PLAYBACK_SEQUENTIAL); + + PoolEntry defaults; + + base_property_helper.set_prefix("stream_"); + base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), defaults.stream, &AudioStreamRandomizer::set_stream, &AudioStreamRandomizer::get_stream); + base_property_helper.register_property(PropertyInfo(Variant::FLOAT, "weight", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), defaults.weight, &AudioStreamRandomizer::set_stream_probability_weight, &AudioStreamRandomizer::get_stream_probability_weight); } -AudioStreamRandomizer::AudioStreamRandomizer() {} +AudioStreamRandomizer::AudioStreamRandomizer() { + property_helper.setup_for_instance(base_property_helper, this); +} void AudioStreamPlaybackRandomizer::start(double p_from_pos) { playing = playback; diff --git a/servers/audio/audio_stream.h b/servers/audio/audio_stream.h index f8123fbe15..01a4a09942 100644 --- a/servers/audio/audio_stream.h +++ b/servers/audio/audio_stream.h @@ -33,6 +33,7 @@ #include "core/io/image.h" #include "core/io/resource.h" +#include "scene/property_list_helper.h" #include "servers/audio/audio_filter_sw.h" #include "servers/audio_server.h" @@ -236,9 +237,12 @@ private: struct PoolEntry { Ref<AudioStream> stream; - float weight; + float weight = 1.0; }; + static inline PropertyListHelper base_property_helper; + PropertyListHelper property_helper; + HashSet<AudioStreamPlaybackRandomizer *> playbacks; Vector<PoolEntry> audio_stream_pool; float random_pitch_scale = 1.0f; @@ -254,9 +258,11 @@ private: protected: static void _bind_methods(); - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; + bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); } + bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); } + void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list, audio_stream_pool.size()); } + bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); } + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); } public: void add_stream(int p_index, Ref<AudioStream> p_stream, float p_weight = 1.0); diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index 27c07f23fa..b5d31f5414 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -1472,7 +1472,7 @@ void SkyRD::update_res_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p Vector<Color> clear_colors; clear_colors.push_back(Color(0.0, 0.0, 0.0)); - RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 0.0); _render_sky(draw_list, p_time, framebuffer, pipeline, material->uniform_set, texture_uniform_set, projection, sky_transform, sky_scene_state.cam_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } @@ -1491,7 +1491,7 @@ void SkyRD::update_res_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RID p Vector<Color> clear_colors; clear_colors.push_back(Color(0.0, 0.0, 0.0)); - RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors); + RD::DrawListID draw_list = RD::get_singleton()->draw_list_begin(framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_DISCARD, clear_colors, 0.0); _render_sky(draw_list, p_time, framebuffer, pipeline, material->uniform_set, texture_uniform_set, projection, sky_transform, sky_scene_state.cam_transform.origin, p_luminance_multiplier); RD::get_singleton()->draw_list_end(); } 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 0e69ad99b8..c7ab7ea462 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -1413,7 +1413,7 @@ void RenderForwardClustered::_pre_opaque_render(RenderDataRD *p_render_data, boo if (p_render_data->directional_shadows.size()) { //open the pass for directional shadows light_storage->update_directional_shadow_atlas(); - RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE); + RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, Vector<Color>(), 0.0); RD::get_singleton()->draw_list_end(); } } @@ -1930,7 +1930,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co } if (needs_pre_resolve) { //pre clear the depth framebuffer, as AMD (and maybe others?) use compute for it, and barrier other compute shaders. - RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pass_clear); + RD::get_singleton()->draw_list_begin(depth_framebuffer, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, depth_pass_clear, 0.0); RD::get_singleton()->draw_list_end(); //start compute processes here, so they run at the same time as depth pre-pass _post_prepass_render(p_render_data, using_sdfgi || using_voxelgi); diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 48c9cda253..5715d94d95 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -614,7 +614,7 @@ void RenderForwardMobile::_pre_opaque_render(RenderDataRD *p_render_data) { if (p_render_data->directional_shadows.size()) { //open the pass for directional shadows light_storage->update_directional_shadow_atlas(); - RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE); + RD::get_singleton()->draw_list_begin(light_storage->direction_shadow_get_fb(), RD::INITIAL_ACTION_DISCARD, RD::FINAL_ACTION_DISCARD, RD::INITIAL_ACTION_CLEAR, RD::FINAL_ACTION_STORE, Vector<Color>(), 0.0); RD::get_singleton()->draw_list_end(); } } diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h index c8277024cf..9a898a2fca 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -1103,7 +1103,7 @@ private: public: DrawListID draw_list_begin_for_screen(DisplayServer::WindowID p_screen = 0, const Color &p_clear_color = Color()); - DrawListID draw_list_begin(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values = Vector<Color>(), float p_clear_depth = 0.0, uint32_t p_clear_stencil = 0, const Rect2 &p_region = Rect2()); + DrawListID draw_list_begin(RID p_framebuffer, InitialAction p_initial_color_action, FinalAction p_final_color_action, InitialAction p_initial_depth_action, FinalAction p_final_depth_action, const Vector<Color> &p_clear_color_values = Vector<Color>(), float p_clear_depth = 1.0, uint32_t p_clear_stencil = 0, const Rect2 &p_region = Rect2()); void draw_list_set_blend_constants(DrawListID p_list, const Color &p_color); void draw_list_bind_render_pipeline(DrawListID p_list, RID p_render_pipeline); diff --git a/tests/core/math/test_transform_3d.h b/tests/core/math/test_transform_3d.h index 551b20fe74..fba0fcb280 100644 --- a/tests/core/math/test_transform_3d.h +++ b/tests/core/math/test_transform_3d.h @@ -107,6 +107,35 @@ TEST_CASE("[Transform3D] Finite number checks") { "Transform3D with two components infinite should not be finite."); } +TEST_CASE("[Transform3D] Rotate around global origin") { + // Start with the default orientation, but not centered on the origin. + // Rotating should rotate both our basis and the origin. + Transform3D transform = Transform3D(); + transform.origin = Vector3(0, 0, 1); + + Transform3D expected = Transform3D(); + expected.origin = Vector3(0, 0, -1); + expected.basis[0] = Vector3(-1, 0, 0); + expected.basis[2] = Vector3(0, 0, -1); + + const Transform3D rotated_transform = transform.rotated(Vector3(0, 1, 0), Math_PI); + CHECK_MESSAGE(rotated_transform.is_equal_approx(expected), "The rotated transform should have a new orientation and basis."); +} + +TEST_CASE("[Transform3D] Rotate in-place (local rotation)") { + // Start with the default orientation. + // Local rotation should not change the origin, only the basis. + Transform3D transform = Transform3D(); + transform.origin = Vector3(1, 2, 3); + + Transform3D expected = Transform3D(); + expected.origin = Vector3(1, 2, 3); + expected.basis[0] = Vector3(-1, 0, 0); + expected.basis[2] = Vector3(0, 0, -1); + + const Transform3D rotated_transform = Transform3D(transform.rotated_local(Vector3(0, 1, 0), Math_PI)); + CHECK_MESSAGE(rotated_transform.is_equal_approx(expected), "The rotated transform should have a new orientation but still be based on the same origin."); +} } // namespace TestTransform3D #endif // TEST_TRANSFORM_3D_H |