diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 208253d6170a..31659d4d4edd 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -835,35 +835,9 @@ ConnectDialog::~ConnectDialog() { ////////////////////////////////////////// -// Originally copied and adapted from EditorProperty, try to keep style in sync. Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const { - // `p_text` is expected to be something like this: - // - `class|Control||Control brief description.`; - // - `signal|gui_input|(event: InputEvent)|gui_input description.`; - // - `../../.. :: _on_gui_input()`. - // Note that the description can be empty or contain `|`. - PackedStringArray slices = p_text.split("|", true, 3); - if (slices.size() < 4) { - return nullptr; // Use default tooltip instead. - } - - String item_type = (slices[0] == "class") ? TTR("Class:") : TTR("Signal:"); - String item_name = slices[1].strip_edges(); - String item_params = slices[2].strip_edges(); - String item_descr = slices[3].strip_edges(); - - String text = item_type + " [u][b]" + item_name + "[/b][/u]" + item_params + "\n"; - if (item_descr.is_empty()) { - text += "[i]" + TTR("No description.") + "[/i]"; - } else { - text += item_descr; - } - - EditorHelpBit *help_bit = memnew(EditorHelpBit); - help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); - help_bit->set_text(text); - - return help_bit; + // If it's not a doc tooltip, fallback to the default one. + return p_text.contains("::") ? nullptr : memnew(EditorHelpTooltip(p_text)); } struct _ConnectionsDockMethodInfoSort { @@ -1341,7 +1315,6 @@ void ConnectionsDock::update_tree() { while (native_base != StringName()) { String class_name; String doc_class_name; - String class_brief; Ref class_icon; List class_signals; @@ -1355,21 +1328,8 @@ void ConnectionsDock::update_tree() { if (doc_class_name.is_empty()) { doc_class_name = script_base->get_path().trim_prefix("res://").quote(); } - - // For a script class, the cache is filled each time. - if (!doc_class_name.is_empty()) { - if (descr_cache.has(doc_class_name)) { - descr_cache[doc_class_name].clear(); - } - HashMap::ConstIterator F = doc_data->class_list.find(doc_class_name); - if (F) { - class_brief = F->value.brief_description; - for (int i = 0; i < F->value.signals.size(); i++) { - descr_cache[doc_class_name][F->value.signals[i].name] = F->value.signals[i].description; - } - } else { - doc_class_name = String(); - } + if (!doc_class_name.is_empty() && !doc_data->class_list.find(doc_class_name)) { + doc_class_name = String(); } class_icon = editor_data.get_script_icon(script_base); @@ -1398,18 +1358,9 @@ void ConnectionsDock::update_tree() { script_base = base; } else { class_name = native_base; - doc_class_name = class_name; + doc_class_name = native_base; - HashMap::ConstIterator F = doc_data->class_list.find(doc_class_name); - if (F) { - class_brief = DTR(F->value.brief_description); - // For a native class, the cache is filled once. - if (!descr_cache.has(doc_class_name)) { - for (int i = 0; i < F->value.signals.size(); i++) { - descr_cache[doc_class_name][F->value.signals[i].name] = DTR(F->value.signals[i].description); - } - } - } else { + if (!doc_data->class_list.find(doc_class_name)) { doc_class_name = String(); } @@ -1434,8 +1385,8 @@ void ConnectionsDock::update_tree() { section_item = tree->create_item(root); section_item->set_text(0, class_name); - // `|` separators used in `make_custom_tooltip()` for formatting. - section_item->set_tooltip_text(0, "class|" + class_name + "||" + class_brief); + // `|` separators used in `EditorHelpTooltip` for formatting. + 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); @@ -1466,22 +1417,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"))); - - // Set tooltip with the signal's documentation. - { - String descr; - - HashMap>::ConstIterator G = descr_cache.find(doc_class_name); - if (G) { - HashMap::ConstIterator F = G->value.find(signal_name); - if (F) { - descr = F->value; - } - } - - // `|` separators used in `make_custom_tooltip()` for formatting. - signal_item->set_tooltip_text(0, "signal|" + String(signal_name) + "|" + signame.trim_prefix(mi.name) + "|" + descr); - } + // `|` separators used in `EditorHelpTooltip` for formatting. + signal_item->set_tooltip_text(0, "signal|" + doc_class_name + "|" + String(signal_name) + "|" + signame.trim_prefix(mi.name)); // List existing connections. List existing_connections; diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index b07b08ecc7c6..7316a770ec91 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -231,8 +231,6 @@ class ConnectionsDock : public VBoxContainer { PopupMenu *slot_menu = nullptr; LineEdit *search_box = nullptr; - HashMap> descr_cache; - void _filter_changed(const String &p_text); void _make_or_edit_connection(); diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 1c8226e5e120..0e025b443093 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -500,10 +500,11 @@ 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); - if (EditorHelp::get_doc_data()->class_list.has(p_type) && !DTR(EditorHelp::get_doc_data()->class_list[p_type].brief_description).is_empty()) { + 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, DTR(EditorHelp::get_doc_data()->class_list[p_type].brief_description))); + 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. diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp index bca30b2d2d19..c3087d797ae7 100644 --- a/editor/editor_build_profile.cpp +++ b/editor/editor_build_profile.cpp @@ -646,24 +646,21 @@ 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 class_name = md; - String class_description; - - DocTools *dd = EditorHelp::get_doc_data(); - HashMap::Iterator E = dd->class_list.find(class_name); - if (E) { - class_description = DTR(E->value.brief_description); + 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->set_text(class_description); } else if (md.get_type() == Variant::INT) { - int build_option_id = md; - String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption(build_option_id)); - - description_bit->set_text(TTRGET(build_option_description)); - return; - } else { - return; + 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)); } } diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index 50406bea6a76..5a44ae1abaac 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -555,21 +555,22 @@ 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 class_name = md; - String class_description; - - DocTools *dd = EditorHelp::get_doc_data(); - HashMap::Iterator E = dd->class_list.find(class_name); - if (E) { - class_description = DTR(E->value.brief_description); + 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->set_text(class_description); } else if (md.get_type() == Variant::INT) { - int feature_id = md; - String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature(feature_id)); + 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_text(TTRGET(feature_description)); return; } else { return; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index b3bcb9f0140e..588b657d0ddd 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -38,6 +38,7 @@ #include "doc_data_compressed.gen.h" #include "editor/editor_node.h" #include "editor/editor_paths.h" +#include "editor/editor_property_name_processor.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" @@ -2587,7 +2588,7 @@ DocTools *EditorHelp::get_doc_data() { return doc; } -//// EditorHelpBit /// +/// EditorHelpBit /// void EditorHelpBit::_go_to_help(String p_what) { EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); @@ -2620,6 +2621,179 @@ void EditorHelpBit::_meta_clicked(String p_select) { } } +String EditorHelpBit::get_class_description(const StringName &p_class_name) const { + if (doc_class_cache.has(p_class_name)) { + return doc_class_cache[p_class_name]; + } + + String description; + HashMap::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); + if (E) { + // Non-native class shouldn't be cached, nor translated. + bool is_native = ClassDB::class_exists(p_class_name); + description = is_native ? DTR(E->value.brief_description) : E->value.brief_description; + + if (is_native) { + doc_class_cache[p_class_name] = description; + } + } + + return description; +} + +String EditorHelpBit::get_property_description(const StringName &p_class_name, const StringName &p_property_name) const { + 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; + // Non-native properties shouldn't be cached, nor translated. + bool is_native = ClassDB::class_exists(p_class_name); + DocTools *dd = EditorHelp::get_doc_data(); + HashMap::ConstIterator E = dd->class_list.find(p_class_name); + if (E) { + for (int i = 0; i < E->value.properties.size(); i++) { + String description_current = is_native ? DTR(E->value.properties[i].description) : E->value.properties[i].description; + + const Vector class_enum = E->value.properties[i].enumeration.split("."); + const String enum_name = class_enum.size() >= 2 ? class_enum[1] : ""; + if (!enum_name.is_empty()) { + // Classes can use enums from other classes, so check from which it came. + HashMap::ConstIterator enum_class = dd->class_list.find(class_enum[0]); + if (enum_class) { + for (DocData::ConstantDoc val : enum_class->value.constants) { + // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector. + if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) { + const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); + const String enum_prefix = EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " "; + const String enum_description = is_native ? DTR(val.description) : val.description; + + // Prettify the enum value display, so that "_" becomes "Value". + description_current = description_current.trim_prefix("\n") + vformat("\n[b]%s:[/b] %s", enum_value.trim_prefix(enum_prefix), enum_description.is_empty() ? ("[i]" + DTR("No description available.") + "[/i]") : enum_description); + } + } + } + } + + if (E->value.properties[i].name == p_property_name) { + description = description_current; + + if (!is_native) { + break; + } + } + + if (is_native) { + doc_property_cache[p_class_name][E->value.properties[i].name] = description_current; + } + } + } + + return description; +} + +String EditorHelpBit::get_method_description(const StringName &p_class_name, const StringName &p_method_name) const { + 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; + HashMap::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); + if (E) { + // Non-native methods shouldn't be cached, nor translated. + bool is_native = ClassDB::class_exists(p_class_name); + + for (int i = 0; i < E->value.methods.size(); i++) { + String description_current = is_native ? DTR(E->value.methods[i].description) : E->value.methods[i].description; + + if (E->value.methods[i].name == p_method_name) { + description = description_current; + + if (!is_native) { + break; + } + } + + if (is_native) { + doc_method_cache[p_class_name][E->value.methods[i].name] = description_current; + } + } + } + + return description; +} + +String EditorHelpBit::get_signal_description(const StringName &p_class_name, const StringName &p_signal_name) const { + 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; + HashMap::ConstIterator E = EditorHelp::get_doc_data()->class_list.find(p_class_name); + if (E) { + // Non-native signals shouldn't be cached, nor translated. + bool is_native = ClassDB::class_exists(p_class_name); + + for (int i = 0; i < E->value.signals.size(); i++) { + String description_current = is_native ? DTR(E->value.signals[i].description) : E->value.signals[i].description; + + if (E->value.signals[i].name == p_signal_name) { + description = description_current; + + if (!is_native) { + break; + } + } + + if (is_native) { + doc_signal_cache[p_class_name][E->value.signals[i].name] = description_current; + } + } + } + + return description; +} + +String EditorHelpBit::get_theme_item_description(const StringName &p_class_name, const StringName &p_theme_item_name) const { + 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; + bool found = false; + DocTools *dd = EditorHelp::get_doc_data(); + HashMap::ConstIterator E = dd->class_list.find(p_class_name); + while (E) { + // Non-native theme items shouldn't be cached, nor translated. + bool is_native = ClassDB::class_exists(p_class_name); + + for (int i = 0; i < E->value.theme_properties.size(); i++) { + String description_current = is_native ? DTR(E->value.theme_properties[i].description) : E->value.theme_properties[i].description; + + if (E->value.theme_properties[i].name == p_theme_item_name) { + description = description_current; + found = true; + + if (!is_native) { + break; + } + } + + if (is_native) { + doc_theme_item_cache[p_class_name][E->value.theme_properties[i].name] = description_current; + } + } + + if (found || E->value.inherits.is_empty()) { + break; + } + // Check for inherited theme items. + E = dd->class_list.find(E->value.inherits); + } + + return description; +} + void EditorHelpBit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_text", "text"), &EditorHelpBit::set_text); ADD_SIGNAL(MethodInfo("request_hide")); @@ -2650,7 +2824,73 @@ EditorHelpBit::EditorHelpBit() { set_custom_minimum_size(Size2(0, 50 * EDSCALE)); } -//// FindBar /// +/// EditorHelpTooltip /// + +void EditorHelpTooltip::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POSTINITIALIZE: { + if (!tooltip_text.is_empty()) { + parse_tooltip(tooltip_text); + } + } break; + } +} + +// `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; + + 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'."); + + String type = slices[0]; + String class_name = slices[1]; + String property_name = slices[2]; + String property_args = slices[3]; + + String title; + String description; + String formatted_text; + + if (type == "class") { + title = class_name; + description = get_class_description(class_name); + formatted_text = TTR("Class:"); + } else { + title = property_name; + + if (type == "property") { + description = get_property_description(class_name, property_name); + 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 Item:"); + } else { + ERR_FAIL_MSG("Invalid tooltip type '" + type + "'. Valid types are 'class', 'property', 'method', 'signal', and 'theme_item'."); + } + } + + formatted_text += " [u][b]" + title + "[/b][/u]" + property_args + "\n"; + formatted_text += description.is_empty() ? "[i]" + TTR("No description available.") + "[/i]" : description; + set_text(formatted_text); +} + +EditorHelpTooltip::EditorHelpTooltip(const String &p_text) { + tooltip_text = p_text; + + get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 0)); +} + +/// FindBar /// FindBar::FindBar() { search_text = memnew(LineEdit); diff --git a/editor/editor_help.h b/editor/editor_help.h index 1f1528945b48..439b62c34f70 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -232,6 +232,12 @@ public: class EditorHelpBit : public MarginContainer { GDCLASS(EditorHelpBit, MarginContainer); + inline static HashMap doc_class_cache; + inline static HashMap> doc_property_cache; + inline static HashMap> doc_method_cache; + inline static HashMap> doc_signal_cache; + inline static HashMap> doc_theme_item_cache; + RichTextLabel *rich_text = nullptr; void _go_to_help(String p_what); void _meta_clicked(String p_select); @@ -243,9 +249,30 @@ protected: 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; + RichTextLabel *get_rich_text() { return rich_text; } void set_text(const String &p_text); + EditorHelpBit(); }; +class EditorHelpTooltip : public EditorHelpBit { + GDCLASS(EditorHelpTooltip, EditorHelpBit); + + String tooltip_text; + +protected: + void _notification(int p_what); + +public: + void parse_tooltip(const String &p_text); + + EditorHelpTooltip(const String &p_text = String()); +}; + #endif // EDITOR_HELP_H diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 382b182e0eda..91a3181747fb 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -905,47 +905,17 @@ void EditorProperty::_update_pin_flags() { } } -static Control *make_help_bit(const String &p_item_type, const String &p_text, const String &p_warning, const Color &p_warn_color) { - // `p_text` is expected to be something like this: - // `item_name|Item description.`. - // Note that the description can be empty or contain `|`. - PackedStringArray slices = p_text.split("|", true, 1); - if (slices.size() < 2) { - return nullptr; // Use default tooltip instead. - } - - String item_name = slices[0].strip_edges(); - String item_descr = slices[1].strip_edges(); - - String text; - if (!p_item_type.is_empty()) { - text = p_item_type + " "; - } - text += "[u][b]" + item_name + "[/b][/u]\n"; - if (item_descr.is_empty()) { - text += "[i]" + TTR("No description.") + "[/i]"; - } else { - text += item_descr; - } - if (!p_warning.is_empty()) { - text += "\n[b][color=" + p_warn_color.to_html(false) + "]" + p_warning + "[/color][/b]"; - } - - EditorHelpBit *help_bit = memnew(EditorHelpBit); - help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); - help_bit->set_text(text); - - return help_bit; -} - Control *EditorProperty::make_custom_tooltip(const String &p_text) const { - String warn; - Color warn_color; + EditorHelpTooltip *tooltip = memnew(EditorHelpTooltip(p_text)); + if (object->has_method("_get_property_warning")) { - warn = object->call("_get_property_warning", property); - warn_color = get_theme_color(SNAME("warning_color")); + String warn = object->call("_get_property_warning", property); + if (!warn.is_empty()) { + tooltip->set_text(tooltip->get_rich_text()->get_text() + "\n[b][color=" + get_theme_color(SNAME("warning_color")).to_html(false) + "]" + warn + "[/color][/b]"); + } } - return make_help_bit(TTR("Property:"), p_text, warn, warn_color); + + return tooltip; } void EditorProperty::menu_option(int p_option) { @@ -1178,7 +1148,8 @@ void EditorInspectorCategory::_notification(int p_what) { } Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const { - return make_help_bit(TTR("Class:"), p_text, String(), Color()); + // Far from perfect solution, as there's nothing that prevents a category from having a name that starts with that. + return p_text.begins_with("class|") ? memnew(EditorHelpTooltip(p_text)) : nullptr; } Size2 EditorInspectorCategory::get_minimum_size() const { @@ -2883,24 +2854,8 @@ void EditorInspector::update_tree() { category->doc_class_name = doc_name; if (use_doc_hints) { - String descr = ""; - // Sets the category tooltip to show documentation. - if (!class_descr_cache.has(doc_name)) { - DocTools *dd = EditorHelp::get_doc_data(); - HashMap::Iterator E = dd->class_list.find(doc_name); - if (E) { - descr = E->value.brief_description; - } - if (ClassDB::class_exists(doc_name)) { - descr = DTR(descr); // Do not translate the class description of scripts. - class_descr_cache[doc_name] = descr; // Do not cache the class description of scripts. - } - } else { - descr = class_descr_cache[doc_name]; - } - - // `|` separator used in `make_help_bit()` for formatting. - category->set_tooltip_text(p.name + "|" + descr); + // `|` separator used in `EditorHelpTooltip` for formatting. + category->set_tooltip_text("class|" + doc_name + "||"); } // Add editors at the start of a category. @@ -3195,13 +3150,12 @@ void EditorInspector::update_tree() { restart_request_props.insert(p.name); } - PropertyDocInfo doc_info; + String doc_path; + String theme_item_name; + StringName classname = doc_name; + // Build the doc hint, to use as tooltip. if (use_doc_hints) { - // Build the doc hint, to use as tooltip. - - // Get the class name. - StringName classname = doc_name; if (!object_class.is_empty()) { classname = object_class; } else if (Object::cast_to(object)) { @@ -3231,83 +3185,55 @@ void EditorInspector::update_tree() { classname = get_edited_object()->get_class(); } - // Search for the property description in the cache. - HashMap>::Iterator E = doc_info_cache.find(classname); + // Search for the doc path in the cache. + HashMap>::Iterator E = doc_path_cache.find(classname); if (E) { - HashMap::Iterator F = E->value.find(propname); + HashMap::Iterator F = E->value.find(propname); if (F) { found = true; - doc_info = F->value; + doc_path = F->value; } } if (!found) { + DocTools *dd = EditorHelp::get_doc_data(); + // Do not cache the doc path information of scripts. bool is_native_class = ClassDB::class_exists(classname); - // Build the property description String and add it to the cache. - DocTools *dd = EditorHelp::get_doc_data(); HashMap::ConstIterator F = dd->class_list.find(classname); - while (F && doc_info.description.is_empty()) { - for (int i = 0; i < F->value.properties.size(); i++) { - if (F->value.properties[i].name == propname.operator String()) { - doc_info.description = F->value.properties[i].description; - if (is_native_class) { - doc_info.description = DTR(doc_info.description); // Do not translate the property description of scripts. - } - - const Vector class_enum = F->value.properties[i].enumeration.split("."); - const String class_name = class_enum[0]; - const String enum_name = class_enum.size() >= 2 ? class_enum[1] : ""; - if (!enum_name.is_empty()) { - HashMap::ConstIterator enum_class = dd->class_list.find(class_name); - if (enum_class) { - for (DocData::ConstantDoc val : enum_class->value.constants) { - // Don't display `_MAX` enum value descriptions, as these are never exposed in the inspector. - if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) { - const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); - // Prettify the enum value display, so that "_" becomes "Value". - String desc = val.description; - if (is_native_class) { - desc = DTR(desc); // Do not translate the enum value description of scripts. - } - desc = desc.trim_prefix("\n"); - doc_info.description += vformat( - "\n[b]%s:[/b] %s", - enum_value.trim_prefix(EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " "), - desc.is_empty() ? ("[i]" + TTR("No description.") + "[/i]") : desc); - } - } - } - } - - doc_info.path = "class_property:" + F->value.name + ":" + F->value.properties[i].name; - break; - } - } - + while (F) { Vector slices = propname.operator String().split("/"); + // Check if it's a theme item first. if (slices.size() == 2 && slices[0].begins_with("theme_override_")) { for (int i = 0; i < F->value.theme_properties.size(); i++) { + String doc_path_current = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name; if (F->value.theme_properties[i].name == slices[1]) { - doc_info.description = F->value.theme_properties[i].description; - if (is_native_class) { - doc_info.description = DTR(doc_info.description); // Do not translate the theme item description of scripts. - } - doc_info.path = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name; - break; + doc_path = doc_path_current; + theme_item_name = F->value.theme_properties[i].name; + } + } + + if (is_native_class) { + doc_path_cache[classname][propname] = doc_path; + } + } else { + for (int i = 0; i < F->value.properties.size(); i++) { + String doc_path_current = "class_property:" + F->value.name + ":" + F->value.properties[i].name; + if (F->value.properties[i].name == propname.operator String()) { + doc_path = doc_path_current; + } + + if (is_native_class) { + doc_path_cache[classname][propname] = doc_path; } } } - if (!F->value.inherits.is_empty()) { - F = dd->class_list.find(F->value.inherits); - } else { + if (!doc_path.is_empty() || F->value.inherits.is_empty()) { break; } - } - - if (is_native_class) { - doc_info_cache[classname][propname] = doc_info; // Do not cache the doc information of scripts. + // Couldn't find the doc path in the class itself, try its super class. + F = dd->class_list.find(F->value.inherits); } } } @@ -3346,11 +3272,11 @@ void EditorInspector::update_tree() { if (properties.size()) { if (properties.size() == 1) { - //since it's one, associate: + // Since it's one, associate: ep->property = properties[0]; ep->property_path = property_prefix + properties[0]; ep->property_usage = p.usage; - //and set label? + // And set label? } if (!editors[i].label.is_empty()) { ep->set_label(editors[i].label); @@ -3398,9 +3324,17 @@ void EditorInspector::update_tree() { ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed)); ep->connect("resource_selected", callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED); ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED); - // `|` separator used in `make_help_bit()` for formatting. - ep->set_tooltip_text(property_prefix + p.name + "|" + doc_info.description); - ep->set_doc_path(doc_info.path); + + if (use_doc_hints) { + // `|` separator used in `EditorHelpTooltip` for formatting. + if (theme_item_name.is_empty()) { + ep->set_tooltip_text("property|" + classname + "|" + property_prefix + p.name + "|"); + } else { + ep->set_tooltip_text("theme_item|" + classname + "|" + theme_item_name + "|"); + } + } + + ep->set_doc_path(doc_path); ep->update_property(); ep->_update_pin_flags(); ep->update_editor_property_status(); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 4393922f52cb..b5f0cec80bcc 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -501,13 +501,7 @@ class EditorInspector : public ScrollContainer { int property_focusable; int update_scroll_request; - struct PropertyDocInfo { - String description; - String path; - }; - - HashMap> doc_info_cache; - HashMap class_descr_cache; + HashMap> doc_path_cache; HashSet restart_request_props; HashMap scroll_cache; diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index 5228db03b958..8c0a5b999af7 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -370,46 +370,15 @@ void PropertySelector::_item_selected() { class_type = instance->get_class(); } - DocTools *dd = EditorHelp::get_doc_data(); String text; - if (properties) { - while (!class_type.is_empty()) { - HashMap::Iterator E = dd->class_list.find(class_type); - if (E) { - for (int i = 0; i < E->value.properties.size(); i++) { - if (E->value.properties[i].name == name) { - text = DTR(E->value.properties[i].description); - break; - } - } - } - - if (!text.is_empty()) { - break; - } - - // The property may be from a parent class, keep looking. - class_type = ClassDB::get_parent_class(class_type); + 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; } - } else { - while (!class_type.is_empty()) { - HashMap::Iterator E = dd->class_list.find(class_type); - if (E) { - for (int i = 0; i < E->value.methods.size(); i++) { - if (E->value.methods[i].name == name) { - text = DTR(E->value.methods[i].description); - break; - } - } - } - if (!text.is_empty()) { - break; - } - - // The method may be from a parent class, keep looking. - class_type = ClassDB::get_parent_class(class_type); - } + // It may be from a parent class, keep looking. + class_type = ClassDB::get_parent_class(class_type); } if (!text.is_empty()) {