1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-13 13:31:48 +00:00

Revamp how documentation tooltips work

This commit is contained in:
Michael Alexsander
2023-09-20 23:54:51 -03:00
parent f5696c311c
commit ae91644c73
10 changed files with 373 additions and 275 deletions

View File

@@ -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<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.
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<String, DocData::ClassDoc>::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<String> 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<String, DocData::ClassDoc>::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 "<ENUM NAME>_<VALUE>" 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<String, DocData::ClassDoc>::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<String, DocData::ClassDoc>::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<String, DocData::ClassDoc>::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);