From 5701057ba45dcbee073a9a6c1829cc211c195ae5 Mon Sep 17 00:00:00 2001 From: nlupugla Date: Sat, 6 Apr 2024 17:58:00 -0400 Subject: [PATCH] Added doc gen for structs. --- SConstruct | 2 + core/doc_data.cpp | 24 ++ core/doc_data.h | 78 ++++ core/object/method_info.cpp | 8 +- core/object/object.cpp | 2 +- core/variant/array.cpp | 6 - core/variant/array.h | 1 - core/variant/struct_generator.h | 14 +- core/variant/typed_array.h | 2 +- core/variant/variant_setget.cpp | 1 + doc/class.xsd | 31 ++ doc/classes/Object.xml | 25 ++ doc/tools/doc_status.py | 9 +- editor/doc_tools.cpp | 668 ++++++++++++++++++++------------ editor/editor_help.cpp | 93 +++++ editor/editor_help.h | 2 + 16 files changed, 685 insertions(+), 281 deletions(-) diff --git a/SConstruct b/SConstruct index 0245531b45e2..0595a304ad62 100644 --- a/SConstruct +++ b/SConstruct @@ -842,6 +842,8 @@ if env.msvc and not methods.using_clang(env): # MSVC ] ) + env.Append(CCFLAGS=["/permissive-"]) + if env["werror"]: env.Append(CCFLAGS=["/WX"]) env.Append(LINKFLAGS=["/WX"]) diff --git a/core/doc_data.cpp b/core/doc_data.cpp index f40e878d52b9..ed6c62765e61 100644 --- a/core/doc_data.cpp +++ b/core/doc_data.cpp @@ -127,6 +127,30 @@ void DocData::property_doc_from_scriptmemberinfo(DocData::PropertyDoc &p_propert p_property.overridden = false; } +void DocData::struct_doc_from_structinfo(DocData::StructDoc &p_struct, const StructInfo &p_structinfo) { + p_struct.name = p_structinfo.name; + // TODO: what about description? + + p_struct.properties.resize(p_structinfo.count); + for (int i = 0; i < p_structinfo.count; i++) { + PropertyDoc property_doc; + property_doc.name = p_structinfo.names[i]; + Variant::Type type = p_structinfo.types[i]; + if (type == Variant::OBJECT) { + property_doc.type = p_structinfo.class_names[i]; + } else if (type == Variant::NIL) { + property_doc.type = "Variant"; + } else { + property_doc.type = Variant::get_type_name(type); + } + if (type != Variant::OBJECT) { + property_doc.default_value = get_default_value_string(p_structinfo.default_values[i]); + } + // TODO: what about description? + p_struct.properties.write[i] = property_doc; + } +} + void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc) { p_method.name = p_methodinfo.name; p_method.description = p_desc; diff --git a/core/doc_data.h b/core/doc_data.h index 6a7f4355db54..562a061bcfd1 100644 --- a/core/doc_data.h +++ b/core/doc_data.h @@ -698,6 +698,67 @@ class DocData { } }; + struct StructDoc { + String name; + String description; + Vector properties; + bool is_deprecated = false; + bool is_experimental = false; + bool operator<(const StructDoc &p_struct) const { + return name < p_struct.name; + } + static StructDoc from_dict(const Dictionary &p_dict) { + StructDoc doc; + + if (p_dict.has("name")) { + doc.name = p_dict["name"]; + } + + if (p_dict.has("description")) { + doc.description = p_dict["description"]; + } + + Array properties; + if (p_dict.has("properties")) { + properties = p_dict["properties"]; + } + for (int i = 0; i < properties.size(); i++) { + doc.properties.push_back(PropertyDoc::from_dict(properties[i])); + } + + if (p_dict.has("is_experimental")) { + doc.is_experimental = p_dict["is_experimental"]; + } + + return doc; + } + static Dictionary to_dict(const StructDoc &p_doc) { + Dictionary dict; + + if (!p_doc.name.is_empty()) { + dict["name"] = p_doc.name; + } + + if (!p_doc.description.is_empty()) { + dict["description"] = p_doc.description; + } + + if (!p_doc.properties.is_empty()) { + Array properties; + for (int i = 0; i < p_doc.properties.size(); i++) { + properties.push_back(PropertyDoc::to_dict(p_doc.properties[i])); + } + dict["properties"] = properties; + } + + dict["is_deprecated"] = p_doc.is_deprecated; + + dict["is_experimental"] = p_doc.is_experimental; + + return dict; + } + }; + struct ClassDoc { String name; String inherits; @@ -713,6 +774,7 @@ class DocData { HashMap enums; Vector properties; Vector annotations; + Vector structs; Vector theme_properties; bool is_deprecated = false; String deprecated_message; @@ -818,6 +880,14 @@ class DocData { doc.annotations.push_back(MethodDoc::from_dict(annotations[i])); } + Array structs; + if (p_dict.has("structs")) { + structs = p_dict["structs"]; + } + for (int i = 0; i < structs.size(); i++) { + doc.structs.push_back(StructDoc::from_dict(structs[i])); + } + Array theme_properties; if (p_dict.has("theme_properties")) { theme_properties = p_dict["theme_properties"]; @@ -947,6 +1017,13 @@ class DocData { dict["annotations"] = annotations; } + if (!p_doc.structs.is_empty()) { + Array structs; + for (int i = 0; i < p_doc.structs.size(); i++) { + structs.push_back(StructDoc::to_dict(p_doc.structs[i])); + } + } + if (!p_doc.theme_properties.is_empty()) { Array theme_properties; for (int i = 0; i < p_doc.theme_properties.size(); i++) { @@ -982,6 +1059,7 @@ class DocData { static void return_doc_from_retinfo(DocData::MethodDoc &p_method, const PropertyInfo &p_retinfo); static void argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const PropertyInfo &p_arginfo); static void property_doc_from_scriptmemberinfo(DocData::PropertyDoc &p_property, const ScriptMemberInfo &p_memberinfo); + static void struct_doc_from_structinfo(DocData::StructDoc &p_struct, const StructInfo &p_structinfo); static void method_doc_from_methodinfo(DocData::MethodDoc &p_method, const MethodInfo &p_methodinfo, const String &p_desc); static void constant_doc_from_variant(DocData::ConstantDoc &p_const, const StringName &p_name, const Variant &p_value, const String &p_desc); static void signal_doc_from_methodinfo(DocData::MethodDoc &p_signal, const MethodInfo &p_methodinfo, const String &p_desc); diff --git a/core/object/method_info.cpp b/core/object/method_info.cpp index 2fb5dc295b33..e79f937ea868 100644 --- a/core/object/method_info.cpp +++ b/core/object/method_info.cpp @@ -75,16 +75,16 @@ MethodInfo MethodInfo::from_dict(const Dictionary &p_dict) { args = p_dict["args"]; } - for (int i = 0; i < args.size(); i++) { - Dictionary d = args[i]; + for (const Variant &arg : args) { + Dictionary d = arg; mi.arguments.push_back(PropertyInfo::from_dict(d)); } Array defargs; if (p_dict.has("default_args")) { defargs = p_dict["default_args"]; } - for (int i = 0; i < defargs.size(); i++) { - mi.default_arguments.push_back(defargs[i]); + for (const Variant &defarg : defargs) { + mi.default_arguments.push_back(defarg); } if (p_dict.has("return")) { diff --git a/core/object/object.cpp b/core/object/object.cpp index d932e1c6b251..c2564b5e9e23 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1597,7 +1597,7 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("get_meta_list"), &Object::_get_meta_list_bind); ClassDB::bind_method(D_METHOD("add_user_signal", "signal", "arguments"), &Object::_add_user_signal, DEFVAL(Array())); - ClassDB::bind_method(D_METHOD("add_user_signal_as_struct", "signal"), &Object::_add_user_signal_as_struct, DEFVAL(Struct())); + ClassDB::bind_method(D_METHOD("add_user_signal_as_struct", "signal"), &Object::_add_user_signal_as_struct); 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); diff --git a/core/variant/array.cpp b/core/variant/array.cpp index a0a7152a29fc..b622f32a83ef 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -1045,12 +1045,6 @@ Array::Array(const StructInfo &p_struct_info, bool is_array_of_structs) { } } -Array::Array(const Vector &p_vec) { - _p = memnew(ArrayPrivate); - _p->refcount.init(); - _p->array = p_vec; // TODO: is this right? Do I need to copy? -} - Array::Array() { _p = memnew(ArrayPrivate); _p->refcount.init(); diff --git a/core/variant/array.h b/core/variant/array.h index 9d7d4b01e300..2d132fa792e5 100644 --- a/core/variant/array.h +++ b/core/variant/array.h @@ -225,7 +225,6 @@ class Array { Array(const Array &p_from, const StructInfo &p_struct_info); Array(const Dictionary &p_from, const StructInfo &p_struct_info); Array(const StructInfo &p_struct_info, bool p_is_array_of_structs = false); - Array(const Vector &p_vec); Array(); ~Array(); }; diff --git a/core/variant/struct_generator.h b/core/variant/struct_generator.h index 54510409e1b6..9da8d272da84 100644 --- a/core/variant/struct_generator.h +++ b/core/variant/struct_generator.h @@ -42,27 +42,27 @@ class Struct; #define STRUCT_DECLARE(m_struct_name) using StructType = m_struct_name #define STRUCT_MEMBER_TYPEDEF_ALIAS(m_type, m_member_name, m_member_name_alias, m_default) \ + using Type = m_type; \ _FORCE_INLINE_ static const StringName get_name() { return SNAME(m_member_name_alias); } \ _FORCE_INLINE_ static Variant get_variant(const StructType &p_struct) { return to_variant(p_struct.m_member_name); } \ _FORCE_INLINE_ static const Variant get_default_value_variant() { return to_variant(m_default); } \ - _FORCE_INLINE_ static m_type get(const StructType &p_struct) { return p_struct.m_member_name; } \ - _FORCE_INLINE_ static m_type get_default_value() { return m_default; } \ + _FORCE_INLINE_ static Type get(const StructType &p_struct) { return p_struct.m_member_name; } \ + _FORCE_INLINE_ static Type get_default_value() { return m_default; } \ _FORCE_INLINE_ static void set_variant(StructType &p_struct, const Variant &p_variant) { p_struct.m_member_name = from_variant(p_variant); } \ - _FORCE_INLINE_ static void set(StructType &p_struct, const m_type &p_value) { p_struct.m_member_name = p_value; } \ - using Type = m_type + _FORCE_INLINE_ static void set(StructType &p_struct, const m_type &p_value) { p_struct.m_member_name = p_value; } #define STRUCT_MEMBER_TYPEDEF(m_type, m_member_name, m_default) \ STRUCT_MEMBER_TYPEDEF_ALIAS(m_type, m_member_name, #m_member_name, m_default) #define STRUCT_MEMBER_TYPEDEF_POINTER(m_type, m_member_name, m_default) \ + using Type = m_type *; \ _FORCE_INLINE_ static const StringName get_name() { return SNAME(#m_member_name); } \ _FORCE_INLINE_ static Variant get_variant(const StructType &p_struct) { return to_variant(p_struct.m_member_name); } \ _FORCE_INLINE_ static const Variant get_default_value_variant() { return to_variant(m_default); } \ - _FORCE_INLINE_ static m_type *get(const StructType &p_struct) { return p_struct.m_member_name; } \ + _FORCE_INLINE_ static Type get(const StructType &p_struct) { return p_struct.m_member_name; } \ _FORCE_INLINE_ static const m_type *get_default_value() { return m_default; } \ _FORCE_INLINE_ static void set_variant(StructType &p_struct, const Variant &p_variant) { p_struct.m_member_name = from_variant(p_variant); } \ - _FORCE_INLINE_ static void set(StructType &p_struct, m_type *p_value) { p_struct.m_member_name = p_value; } \ - using Type = m_type * + _FORCE_INLINE_ static void set(StructType &p_struct, Type p_value) { p_struct.m_member_name = p_value; } #define STRUCT_MEMBER_PRIMITIVE_ALIAS(m_type, m_member_name, m_member_name_alias, m_default) \ m_type m_member_name = m_default; \ diff --git a/core/variant/typed_array.h b/core/variant/typed_array.h index 5d32a498f2a9..7cada0e2f705 100644 --- a/core/variant/typed_array.h +++ b/core/variant/typed_array.h @@ -90,7 +90,7 @@ class TypedArray> : public Array { } _FORCE_INLINE_ TypedArray(const List *p_list) { initialize_struct_type(T::get_struct_info(), true); - for (const List::Element *E = p_list->front(); E; E = E->next()) { + for (const typename List::Element *E = p_list->front(); E; E = E->next()) { push_back(Struct(E->get())); } } diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index 41adbdd4d95f..400fa0b4cb4a 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -1301,6 +1301,7 @@ void Variant::get_property_list(List *p_list) const { } break; } + [[fallthrough]]; } default: { List members; diff --git a/doc/class.xsd b/doc/class.xsd index 28e02e870d94..b08ae97f2fb6 100644 --- a/doc/class.xsd +++ b/doc/class.xsd @@ -196,6 +196,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index b255924e0be1..712a4e2323d4 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -1138,4 +1138,29 @@ Reference-counted connections can be assigned to the same [Callable] multiple times. Each disconnection decreases the internal counter. The signal fully disconnects only when the counter reaches 0. + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/tools/doc_status.py b/doc/tools/doc_status.py index 57c03bfdeef9..b151329d6ffc 100755 --- a/doc/tools/doc_status.py +++ b/doc/tools/doc_status.py @@ -66,6 +66,7 @@ "description", "methods", "constants", + "structs", "members", "theme_items", "signals", @@ -78,6 +79,7 @@ "Desc.", "Methods", "Constants", + "Structs", "Members", "Theme Items", "Signals", @@ -191,6 +193,7 @@ def __init__(self, name: str = ""): self.progresses: Dict[str, ClassStatusProgress] = { "methods": ClassStatusProgress(), "constants": ClassStatusProgress(), + "structs": ClassStatusProgress(), "members": ClassStatusProgress(), "theme_items": ClassStatusProgress(), "signals": ClassStatusProgress(), @@ -239,7 +242,7 @@ def make_output(self) -> Dict[str, str]: ) items_progress = ClassStatusProgress() - for k in ["methods", "constants", "members", "theme_items", "signals", "constructors", "operators"]: + for k in ["methods", "constants", "structs", "members", "theme_items", "signals", "constructors", "operators"]: items_progress += self.progresses[k] output[k] = self.progresses[k].to_configured_colored_string() @@ -284,7 +287,7 @@ def generate_for_class(c: ET.Element): descr = sub_tag.find("description") has_descr = (descr is not None) and (descr.text is not None) and len(descr.text.strip()) > 0 status.progresses[tag.tag].increment(is_deprecated or is_experimental or has_descr) - elif tag.tag in ["constants", "members", "theme_items"]: + elif tag.tag in ["constants", "structs", "members", "theme_items"]: for sub_tag in list(tag): if sub_tag.text is not None: is_deprecated = "deprecated" in sub_tag.attrib @@ -327,7 +330,7 @@ def generate_for_class(c: ET.Element): sys.exit(1) if flags["i"]: - for r in ["methods", "constants", "members", "signals", "theme_items"]: + for r in ["methods", "constants", "structs", "members", "signals", "theme_items"]: index = table_columns.index(r) del table_column_names[index] del table_columns[index] diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index 79e0c7ebd1bc..5c40a6e6c2d6 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -258,6 +258,32 @@ static void merge_properties(Vector &p_to, const Vector &p_to, const Vector &p_from) { + // Get data from `p_to`, to avoid mutation checks. Searching will be done in the sorted `p_to` from the (potentially) unsorted `p_from`. + DocData::StructDoc *to_ptrw = p_to.ptrw(); + int64_t to_size = p_to.size(); + + SearchArray search_array; + + for (const DocData::StructDoc &from : p_from) { + int64_t found = search_array.bisect(to_ptrw, to_size, from, true); + + if (found >= to_size) { + continue; + } + + DocData::StructDoc &to = to_ptrw[found]; + + // Check found entry on name and data type. + if (to.name == from.name) { + to.description = from.description; + to.is_deprecated = from.is_deprecated; + to.is_experimental = from.is_experimental; + merge_properties(to.properties, from.properties); + } + } +} + static void merge_theme_properties(Vector &p_to, const Vector &p_from) { // Get data from `p_to`, to avoid mutation checks. Searching will be done in the sorted `p_to` from the (potentially) unsorted `p_from`. DocData::ThemeItemDoc *to_ptrw = p_to.ptrw(); @@ -345,6 +371,8 @@ void DocTools::merge_from(const DocTools &p_data) { merge_properties(c.properties, cf.properties); + merge_structs(c.structs, cf.structs); + merge_theme_properties(c.theme_properties, cf.theme_properties); merge_operators(c.operators, cf.operators); @@ -679,6 +707,15 @@ void DocTools::generate(BitField p_flags) { c.constants.push_back(constant); } + List struct_list; + ClassDB::get_struct_list(name, &struct_list, true); + + for (const StructInfo &E : struct_list) { + DocData::StructDoc struct_doc; + DocData::struct_doc_from_structinfo(struct_doc, E); + c.structs.push_back(struct_doc); + } + // Theme items. { List theme_items; @@ -1114,85 +1151,83 @@ static Error _parse_methods(Ref &parser, Vector & String element = section.substr(0, section.length() - 1); while (parser->read() == OK) { - if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { - if (parser->get_node_name() == element) { - DocData::MethodDoc method; - ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); - method.name = parser->get_named_attribute_value("name"); - if (parser->has_attribute("qualifiers")) { - method.qualifiers = parser->get_named_attribute_value("qualifiers"); - } + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == section) { + break; + } + if (parser->get_node_type() != XMLParser::NODE_ELEMENT) { + continue; + } + ERR_FAIL_COND_V_MSG(parser->get_node_name() != element, ERR_FILE_CORRUPT, "Invalid tag in doc file: " + parser->get_node_name() + ", expected " + element + "."); + DocData::MethodDoc method; + ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); + method.name = parser->get_named_attribute_value("name"); + if (parser->has_attribute("qualifiers")) { + method.qualifiers = parser->get_named_attribute_value("qualifiers"); + } #ifndef DISABLE_DEPRECATED - if (parser->has_attribute("is_deprecated")) { - method.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true"; - } - if (parser->has_attribute("is_experimental")) { - method.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true"; - } + if (parser->has_attribute("is_deprecated")) { + method.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true"; + } + if (parser->has_attribute("is_experimental")) { + method.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true"; + } #endif - if (parser->has_attribute("deprecated")) { - method.is_deprecated = true; - method.deprecated_message = parser->get_named_attribute_value("deprecated"); - } - if (parser->has_attribute("experimental")) { - method.is_experimental = true; - method.experimental_message = parser->get_named_attribute_value("experimental"); - } - if (parser->has_attribute("keywords")) { - method.keywords = parser->get_named_attribute_value("keywords"); - } - - while (parser->read() == OK) { - if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { - String name = parser->get_node_name(); - if (name == "return") { - ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT); - method.return_type = parser->get_named_attribute_value("type"); - if (parser->has_attribute("enum")) { - method.return_enum = parser->get_named_attribute_value("enum"); - if (parser->has_attribute("is_bitfield")) { - method.return_is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true"; - } - } - } else if (name == "returns_error") { - ERR_FAIL_COND_V(!parser->has_attribute("number"), ERR_FILE_CORRUPT); - method.errors_returned.push_back(parser->get_named_attribute_value("number").to_int()); - } else if (name == "param") { - DocData::ArgumentDoc argument; - ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); - argument.name = parser->get_named_attribute_value("name"); - ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT); - argument.type = parser->get_named_attribute_value("type"); - if (parser->has_attribute("enum")) { - argument.enumeration = parser->get_named_attribute_value("enum"); - if (parser->has_attribute("is_bitfield")) { - argument.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true"; - } - } - - method.arguments.push_back(argument); - - } else if (name == "description") { - parser->read(); - if (parser->get_node_type() == XMLParser::NODE_TEXT) { - method.description = parser->get_node_data(); - } - } + if (parser->has_attribute("deprecated")) { + method.is_deprecated = true; + method.deprecated_message = parser->get_named_attribute_value("deprecated"); + } + if (parser->has_attribute("experimental")) { + method.is_experimental = true; + method.experimental_message = parser->get_named_attribute_value("experimental"); + } + if (parser->has_attribute("keywords")) { + method.keywords = parser->get_named_attribute_value("keywords"); + } - } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == element) { - break; + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == element) { + break; + } + if (parser->get_node_type() != XMLParser::NODE_ELEMENT) { + continue; + } + String name = parser->get_node_name(); + if (name == "return") { + ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT); + method.return_type = parser->get_named_attribute_value("type"); + if (parser->has_attribute("enum")) { + method.return_enum = parser->get_named_attribute_value("enum"); + if (parser->has_attribute("is_bitfield")) { + method.return_is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true"; + } + } + } else if (name == "returns_error") { + ERR_FAIL_COND_V(!parser->has_attribute("number"), ERR_FILE_CORRUPT); + method.errors_returned.push_back(parser->get_named_attribute_value("number").to_int()); + } else if (name == "param") { + DocData::ArgumentDoc argument; + ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); + argument.name = parser->get_named_attribute_value("name"); + ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT); + argument.type = parser->get_named_attribute_value("type"); + if (parser->has_attribute("enum")) { + argument.enumeration = parser->get_named_attribute_value("enum"); + if (parser->has_attribute("is_bitfield")) { + argument.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true"; } } - methods.push_back(method); + method.arguments.push_back(argument); - } else { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + parser->get_node_name() + ", expected " + element + "."); + } else if (name == "description") { + parser->read(); + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + method.description = parser->get_node_data(); + } } - - } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == section) { - break; } + + methods.push_back(method); } return OK; @@ -1269,7 +1304,7 @@ Error DocTools::_load(Ref parser) { ERR_FAIL_COND_V(parser->get_node_name() != "class", ERR_FILE_CORRUPT); ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); - String name = parser->get_named_attribute_value("name"); + const String name = parser->get_named_attribute_value("name"); class_list[name] = DocData::ClassDoc(); DocData::ClassDoc &c = class_list[name]; @@ -1302,216 +1337,286 @@ Error DocTools::_load(Ref parser) { } while (parser->read() == OK) { - if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { - String name2 = parser->get_node_name(); + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "class") { + break; // End of . + } + if (parser->get_node_type() != XMLParser::NODE_ELEMENT) { + continue; + } + const String tag = parser->get_node_name(); - if (name2 == "brief_description") { - parser->read(); - if (parser->get_node_type() == XMLParser::NODE_TEXT) { - c.brief_description = parser->get_node_data(); - } + if (tag == "brief_description") { + parser->read(); + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + c.brief_description = parser->get_node_data(); + } - } else if (name2 == "description") { + } else if (tag == "description") { + parser->read(); + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + c.description = parser->get_node_data(); + } + } else if (tag == "tutorials") { + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "tutorials") { + break; // End of . + } + if (parser->get_node_type() != XMLParser::NODE_ELEMENT) { + continue; + } + const String link_tag = parser->get_node_name(); + ERR_FAIL_COND_V_MSG(link_tag != "link", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + link_tag + ", expected link."); + DocData::TutorialDoc tutorial; + if (parser->has_attribute("title")) { + tutorial.title = parser->get_named_attribute_value("title"); + } parser->read(); if (parser->get_node_type() == XMLParser::NODE_TEXT) { - c.description = parser->get_node_data(); + tutorial.link = parser->get_node_data().strip_edges(); + c.tutorials.push_back(tutorial); } - } else if (name2 == "tutorials") { - while (parser->read() == OK) { - if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { - String name3 = parser->get_node_name(); - - if (name3 == "link") { - DocData::TutorialDoc tutorial; - if (parser->has_attribute("title")) { - tutorial.title = parser->get_named_attribute_value("title"); - } - parser->read(); - if (parser->get_node_type() == XMLParser::NODE_TEXT) { - tutorial.link = parser->get_node_data().strip_edges(); - c.tutorials.push_back(tutorial); - } - } else { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + "."); - } - } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "tutorials") { - break; // End of . + } + } else if (tag == "constructors") { + const Error err2 = _parse_methods(parser, c.constructors); + ERR_FAIL_COND_V(err2, err2); + } else if (tag == "methods") { + const Error err2 = _parse_methods(parser, c.methods); + ERR_FAIL_COND_V(err2, err2); + } else if (tag == "operators") { + const Error err2 = _parse_methods(parser, c.operators); + ERR_FAIL_COND_V(err2, err2); + } else if (tag == "signals") { + const Error err2 = _parse_methods(parser, c.signals); + ERR_FAIL_COND_V(err2, err2); + } else if (tag == "annotations") { + const Error err2 = _parse_methods(parser, c.annotations); + ERR_FAIL_COND_V(err2, err2); + } else if (tag == "members") { + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "members") { + break; // End of . + } + if (parser->get_node_type() != XMLParser::NODE_ELEMENT) { + continue; + } + String member_tag = parser->get_node_name(); + ERR_FAIL_COND_V_MSG(member_tag != "member", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + member_tag + ", expected member."); + DocData::PropertyDoc property_doc; + + ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); + property_doc.name = parser->get_named_attribute_value("name"); + ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT); + property_doc.type = parser->get_named_attribute_value("type"); + if (parser->has_attribute("setter")) { + property_doc.setter = parser->get_named_attribute_value("setter"); + } + if (parser->has_attribute("getter")) { + property_doc.getter = parser->get_named_attribute_value("getter"); + } + if (parser->has_attribute("enum")) { + property_doc.enumeration = parser->get_named_attribute_value("enum"); + if (parser->has_attribute("is_bitfield")) { + property_doc.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true"; } } - } else if (name2 == "constructors") { - Error err2 = _parse_methods(parser, c.constructors); - ERR_FAIL_COND_V(err2, err2); - } else if (name2 == "methods") { - Error err2 = _parse_methods(parser, c.methods); - ERR_FAIL_COND_V(err2, err2); - } else if (name2 == "operators") { - Error err2 = _parse_methods(parser, c.operators); - ERR_FAIL_COND_V(err2, err2); - } else if (name2 == "signals") { - Error err2 = _parse_methods(parser, c.signals); - ERR_FAIL_COND_V(err2, err2); - } else if (name2 == "annotations") { - Error err2 = _parse_methods(parser, c.annotations); - ERR_FAIL_COND_V(err2, err2); - } else if (name2 == "members") { - while (parser->read() == OK) { - if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { - String name3 = parser->get_node_name(); - - if (name3 == "member") { - DocData::PropertyDoc prop2; - - ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); - prop2.name = parser->get_named_attribute_value("name"); - ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT); - prop2.type = parser->get_named_attribute_value("type"); - if (parser->has_attribute("setter")) { - prop2.setter = parser->get_named_attribute_value("setter"); - } - if (parser->has_attribute("getter")) { - prop2.getter = parser->get_named_attribute_value("getter"); - } - if (parser->has_attribute("enum")) { - prop2.enumeration = parser->get_named_attribute_value("enum"); - if (parser->has_attribute("is_bitfield")) { - prop2.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true"; - } - } #ifndef DISABLE_DEPRECATED - if (parser->has_attribute("is_deprecated")) { - prop2.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true"; - } - if (parser->has_attribute("is_experimental")) { - prop2.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true"; - } + if (parser->has_attribute("is_deprecated")) { + property_doc.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true"; + } + if (parser->has_attribute("is_experimental")) { + property_doc.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true"; + } #endif - if (parser->has_attribute("deprecated")) { - prop2.is_deprecated = true; - prop2.deprecated_message = parser->get_named_attribute_value("deprecated"); - } - if (parser->has_attribute("experimental")) { - prop2.is_experimental = true; - prop2.experimental_message = parser->get_named_attribute_value("experimental"); - } - if (parser->has_attribute("keywords")) { - prop2.keywords = parser->get_named_attribute_value("keywords"); - } - if (!parser->is_empty()) { - parser->read(); - if (parser->get_node_type() == XMLParser::NODE_TEXT) { - prop2.description = parser->get_node_data(); - } - } - c.properties.push_back(prop2); - } else { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + "."); - } - - } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "members") { - break; // End of . + if (parser->has_attribute("deprecated")) { + property_doc.is_deprecated = true; + property_doc.deprecated_message = parser->get_named_attribute_value("deprecated"); + } + if (parser->has_attribute("experimental")) { + property_doc.is_experimental = true; + property_doc.experimental_message = parser->get_named_attribute_value("experimental"); + } + if (parser->has_attribute("keywords")) { + property_doc.keywords = parser->get_named_attribute_value("keywords"); + } + if (!parser->is_empty()) { + parser->read(); + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + property_doc.description = parser->get_node_data(); } } + c.properties.push_back(property_doc); + } + } else if (tag == "structs") { + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "structs") { + break; // End of . + } + if (parser->get_node_type() != XMLParser::NODE_ELEMENT) { + continue; + } + const String struct_tag = parser->get_node_name(); + ERR_FAIL_COND_V_MSG(struct_tag != "struct", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + struct_tag + ", expected struct."); + + DocData::StructDoc struct_doc; + + ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); + struct_doc.name = parser->get_named_attribute_value("name"); + + if (parser->has_attribute("is_deprecated")) { + struct_doc.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true"; + } + + if (parser->has_attribute("is_experimental")) { + struct_doc.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true"; + } - } else if (name2 == "theme_items") { while (parser->read() == OK) { - if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { - String name3 = parser->get_node_name(); - - if (name3 == "theme_item") { - DocData::ThemeItemDoc prop2; - - ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); - prop2.name = parser->get_named_attribute_value("name"); - ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT); - prop2.type = parser->get_named_attribute_value("type"); - ERR_FAIL_COND_V(!parser->has_attribute("data_type"), ERR_FILE_CORRUPT); - prop2.data_type = parser->get_named_attribute_value("data_type"); - if (parser->has_attribute("deprecated")) { - prop2.is_deprecated = true; - prop2.deprecated_message = parser->get_named_attribute_value("deprecated"); - } - if (parser->has_attribute("experimental")) { - prop2.is_experimental = true; - prop2.experimental_message = parser->get_named_attribute_value("experimental"); - } - if (parser->has_attribute("keywords")) { - prop2.keywords = parser->get_named_attribute_value("keywords"); - } - if (!parser->is_empty()) { - parser->read(); - if (parser->get_node_type() == XMLParser::NODE_TEXT) { - prop2.description = parser->get_node_data(); - } - } - c.theme_properties.push_back(prop2); - } else { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + "."); + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "struct") { + break; // End of + } + if (parser->get_node_type() != XMLParser::NODE_ELEMENT) { + continue; + } + const String member_tag = parser->get_node_name(); + ERR_FAIL_COND_V_MSG(member_tag != "member", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + member_tag + ", expected member."); + DocData::PropertyDoc member_doc; + + ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); + member_doc.name = parser->get_named_attribute_value("name"); + + ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT); + member_doc.type = parser->get_named_attribute_value("type"); + + ERR_FAIL_COND_V(!parser->has_attribute("default"), ERR_FILE_CORRUPT); + member_doc.default_value = parser->get_named_attribute_value("default"); + + if (parser->has_attribute("enum")) { + member_doc.enumeration = parser->get_named_attribute_value("enum"); + if (parser->has_attribute("is_bitfield")) { + member_doc.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true"; } + } - } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "theme_items") { - break; // End of . + if (parser->has_attribute("is_deprecated")) { + member_doc.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true"; } + + if (parser->has_attribute("is_experimental")) { + member_doc.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true"; + } + + // TODO: figure out description + + // if (!parser->is_empty()) { + // parser->read(); + // if (parser->get_node_type() == XMLParser::NODE_TEXT) { + // member_doc.description = parser->get_node_data().strip_edges(); + // } + // } + + struct_doc.properties.push_back(member_doc); } - } else if (name2 == "constants") { - while (parser->read() == OK) { - if (parser->get_node_type() == XMLParser::NODE_ELEMENT) { - String name3 = parser->get_node_name(); - - if (name3 == "constant") { - DocData::ConstantDoc constant2; - ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); - constant2.name = parser->get_named_attribute_value("name"); - ERR_FAIL_COND_V(!parser->has_attribute("value"), ERR_FILE_CORRUPT); - constant2.value = parser->get_named_attribute_value("value"); - constant2.is_value_valid = true; - if (parser->has_attribute("enum")) { - constant2.enumeration = parser->get_named_attribute_value("enum"); - if (parser->has_attribute("is_bitfield")) { - constant2.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true"; - } - } + // if (!parser->is_empty()) { + // parser->read(); + // if (parser->get_node_type() == XMLParser::NODE_TEXT) { + // struct_doc.description = parser->get_node_data().strip_edges(); + // } + // } + + c.structs.push_back(struct_doc); + } + } else if (tag == "theme_items") { + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "theme_items") { + break; // End of . + } + if (parser->get_node_type() != XMLParser::NODE_ELEMENT) { + continue; + } + const String theme_item_tag = parser->get_node_name(); + ERR_FAIL_COND_V_MSG(theme_item_tag != "theme_item", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + theme_item_tag + ", expected theme_item."); + + DocData::ThemeItemDoc theme_item_doc; + + ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); + theme_item_doc.name = parser->get_named_attribute_value("name"); + ERR_FAIL_COND_V(!parser->has_attribute("type"), ERR_FILE_CORRUPT); + theme_item_doc.type = parser->get_named_attribute_value("type"); + ERR_FAIL_COND_V(!parser->has_attribute("data_type"), ERR_FILE_CORRUPT); + theme_item_doc.data_type = parser->get_named_attribute_value("data_type"); + if (parser->has_attribute("deprecated")) { + theme_item_doc.is_deprecated = true; + theme_item_doc.deprecated_message = parser->get_named_attribute_value("deprecated"); + } + if (parser->has_attribute("experimental")) { + theme_item_doc.is_experimental = true; + theme_item_doc.experimental_message = parser->get_named_attribute_value("experimental"); + } + if (parser->has_attribute("keywords")) { + theme_item_doc.keywords = parser->get_named_attribute_value("keywords"); + } + if (!parser->is_empty()) { + parser->read(); + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + theme_item_doc.description = parser->get_node_data(); + } + } + c.theme_properties.push_back(theme_item_doc); + } + } else if (tag == "constants") { + while (parser->read() == OK) { + if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "constants") { + break; // End of . + } + if (parser->get_node_type() != XMLParser::NODE_ELEMENT) { + continue; + } + const String constant_tag = parser->get_node_name(); + + ERR_FAIL_COND_V_MSG(constant_tag != "constant", ERR_FILE_CORRUPT, "Invalid tag in doc file: " + constant_tag + ", expected constant."); + + DocData::ConstantDoc constant_doc; + ERR_FAIL_COND_V(!parser->has_attribute("name"), ERR_FILE_CORRUPT); + constant_doc.name = parser->get_named_attribute_value("name"); + ERR_FAIL_COND_V(!parser->has_attribute("value"), ERR_FILE_CORRUPT); + constant_doc.value = parser->get_named_attribute_value("value"); + constant_doc.is_value_valid = true; + if (parser->has_attribute("enum")) { + constant_doc.enumeration = parser->get_named_attribute_value("enum"); + if (parser->has_attribute("is_bitfield")) { + constant_doc.is_bitfield = parser->get_named_attribute_value("is_bitfield").to_lower() == "true"; + } + } #ifndef DISABLE_DEPRECATED - if (parser->has_attribute("is_deprecated")) { - constant2.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true"; - } - if (parser->has_attribute("is_experimental")) { - constant2.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true"; - } + if (parser->has_attribute("is_deprecated")) { + constant_doc.is_deprecated = parser->get_named_attribute_value("is_deprecated").to_lower() == "true"; + } + if (parser->has_attribute("is_experimental")) { + constant_doc.is_experimental = parser->get_named_attribute_value("is_experimental").to_lower() == "true"; + } #endif - if (parser->has_attribute("deprecated")) { - constant2.is_deprecated = true; - constant2.deprecated_message = parser->get_named_attribute_value("deprecated"); - } - if (parser->has_attribute("experimental")) { - constant2.is_experimental = true; - constant2.experimental_message = parser->get_named_attribute_value("experimental"); - } - if (parser->has_attribute("keywords")) { - constant2.keywords = parser->get_named_attribute_value("keywords"); - } - if (!parser->is_empty()) { - parser->read(); - if (parser->get_node_type() == XMLParser::NODE_TEXT) { - constant2.description = parser->get_node_data(); - } - } - c.constants.push_back(constant2); - } else { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name3 + "."); - } - - } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "constants") { - break; // End of . + if (parser->has_attribute("deprecated")) { + constant_doc.is_deprecated = true; + constant_doc.deprecated_message = parser->get_named_attribute_value("deprecated"); + } + if (parser->has_attribute("experimental")) { + constant_doc.is_experimental = true; + constant_doc.experimental_message = parser->get_named_attribute_value("experimental"); + } + if (parser->has_attribute("keywords")) { + constant_doc.keywords = parser->get_named_attribute_value("keywords"); + } + if (!parser->is_empty()) { + parser->read(); + if (parser->get_node_type() == XMLParser::NODE_TEXT) { + constant_doc.description = parser->get_node_data(); } } - - } else { - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + name2 + "."); + c.constants.push_back(constant_doc); } - - } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END && parser->get_node_name() == "class") { - break; // End of . + } else { + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, "Invalid tag in doc file: " + tag + "."); } } @@ -1743,6 +1848,53 @@ Error DocTools::save_classes(const String &p_default_path, const HashMap"); + for (int i = 0; i < c.structs.size(); i++) { + const DocData::StructDoc &s = c.structs[i]; + + String struct_header = "" + s.description.xml_escape() + ""); + } + + for (int j = 0; j < s.properties.size(); j++) { + const DocData::PropertyDoc &m = s.properties[j]; + String member_line = ""); + } + _write_string(f, 1, ""); + } + if (!c.theme_properties.is_empty()) { _write_string(f, 1, ""); for (int i = 0; i < c.theme_properties.size(); i++) { diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index cfe257fcfc8d..0e21ded0bbb1 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -282,6 +282,9 @@ void EditorHelp::_class_desc_select(const String &p_select) { } else if (tag == "theme_item") { topic = "class_theme_item"; table = &theme_property_line; + } else if (tag == "struct") { + topic = "struct"; + table = &struct_line; } else { return; } @@ -1640,6 +1643,92 @@ void EditorHelp::_update_doc() { } } + // Structs + if (!cd.structs.is_empty()) { + class_desc->add_newline(); + class_desc->add_newline(); + + section_line.push_back(Pair(TTR("Structs"), class_desc->get_paragraph_count() - 2)); + _push_title_font(); + class_desc->add_text(TTR("Structs")); + _pop_title_font(); + + for (const DocData::StructDoc &struct_item : cd.structs) { + class_desc->add_newline(); + class_desc->add_newline(); + + struct_line[struct_item.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description. + + class_desc->push_indent(1); + _push_code_font(); + + // Struct name. + class_desc->push_color(theme_cache.headline_color); + class_desc->add_text(struct_item.name); + class_desc->pop(); // color + + _pop_code_font(); + + // Struct description. + class_desc->push_indent(1); + _push_normal_font(); + class_desc->push_color(theme_cache.comment_color); + + const String descr = DTR(struct_item.description).strip_edges(); + if (!descr.is_empty()) { + _add_text(descr); + } else { + class_desc->add_image(get_editor_theme_icon(SNAME("Error"))); + class_desc->add_text(" "); + class_desc->push_color(theme_cache.comment_color); + if (cd.is_script_doc) { + class_desc->add_text(TTR("There is currently no description for this struct.")); + } else { + class_desc->append_text(TTR("There is currently no description for this struct. Please help us by [color=$color][url=$url]contributing one[/url][/color]!").replace("$url", CONTRIBUTE_URL).replace("$color", link_color_text)); + } + class_desc->pop(); // color + } + + class_desc->pop(); // color + _pop_normal_font(); + class_desc->pop(); // indent + + // Struct members. + for (const DocData::PropertyDoc &member : struct_item.properties) { + class_desc->add_newline(); + class_desc->push_indent(1); + _push_code_font(); + + // Member type and name. + _add_bulletpoint(); + _add_type(member.type); + class_desc->add_text(" " + member.name); + + // Member default value. + if (!member.default_value.is_empty()) { + class_desc->push_color(theme_cache.symbol_color); + class_desc->add_text(" [" + TTR("default:") + " "); + class_desc->pop(); // color + + class_desc->push_color(theme_cache.value_color); + class_desc->add_text(_fix_constant(member.default_value)); + class_desc->pop(); // color + + class_desc->push_color(theme_cache.symbol_color); + class_desc->add_text("]"); + class_desc->pop(); // color + } + + _pop_code_font(); + class_desc->pop(); // indent + } + + class_desc->pop(); // indent for struct members + class_desc->add_newline(); + } + class_desc->add_newline(); + } + // Constants and enums if (!cd.constants.is_empty()) { HashMap> enums; @@ -2345,6 +2434,10 @@ void EditorHelp::_help_callback(const String &p_topic) { if (enum_line.has(name)) { line = enum_line[name]; } + } else if (what == "class_struct") { + if (struct_line.has(name)) { + line = struct_line[name]; + } } else if (what == "class_theme_item") { if (theme_property_line.has(name)) { line = theme_property_line[name]; diff --git a/editor/editor_help.h b/editor/editor_help.h index 93f74cb2c123..792f59b6ea47 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -110,6 +110,8 @@ class EditorHelp : public VBoxContainer { HashMap annotation_line; HashMap enum_line; HashMap> enum_values_line; + HashMap struct_line; + HashMap> struct_members_line; int description_line = 0; RichTextLabel *class_desc = nullptr;