From 6e3dc38835c5ed021662d88b1c072ea2f2eaf2cd Mon Sep 17 00:00:00 2001 From: Danil Alexeev Date: Sun, 15 Jun 2025 22:58:24 +0300 Subject: [PATCH] Editor: Improve appearance of built-in help --- editor/doc/editor_help.cpp | 307 +++++++++++++-------------- editor/doc/editor_help.h | 2 + editor/themes/theme_classic.cpp | 1 + editor/themes/theme_modern.cpp | 1 + modules/gdscript/gdscript_parser.cpp | 33 ++- scene/gui/rich_text_label.cpp | 1 + 6 files changed, 173 insertions(+), 172 deletions(-) diff --git a/editor/doc/editor_help.cpp b/editor/doc/editor_help.cpp index 4aae326069f..ea6c2d4a711 100644 --- a/editor/doc/editor_help.cpp +++ b/editor/doc/editor_help.cpp @@ -102,11 +102,6 @@ const Vector classes_with_csharp_differences = { }; #endif -static const char32_t nbsp_chr = 160; -static const String nbsp = String::chr(nbsp_chr); -static const String nbsp_equal_nbsp = nbsp + "=" + nbsp; -static const String colon_nbsp = ":" + nbsp; - const Vector packed_array_types = { "PackedByteArray", "PackedColorArray", @@ -120,8 +115,22 @@ const Vector packed_array_types = { "PackedVector4Array", }; -static String _replace_nbsp_with_space(const String &p_string) { - return p_string.replace_char(nbsp_chr, ' '); +static const char32_t nbsp_chr = 160; +static const String nbsp = String::chr(nbsp_chr); +static const String nbsp_equal_nbsp = nbsp + "=" + nbsp; +static const String colon_nbsp = ":" + nbsp; + +static const char32_t wj_chr = 8288; +static const String cr_wj = "\r" + String::chr(wj_chr); + +static String _fix_newlines(const String &p_string) { + // `\n` starts a new paragraph, `\r` just adds a break to existing one. + // Add a non-printable character "WORD JOINER" so that multi-breaks work correctly (`RichTextLabel` bug?). + return p_string.replace("\n", cr_wj); +} + +static String _fix_selection(const String &p_string) { + return p_string.replace_char(nbsp_chr, ' ').remove_char(wj_chr); } static String _fix_constant(const String &p_constant) { @@ -209,6 +218,8 @@ void EditorHelp::_update_theme_item_cache() { theme_cache.qualifier_color = get_theme_color(SNAME("qualifier_color"), SNAME("EditorHelp")); theme_cache.type_color = get_theme_color(SNAME("type_color"), SNAME("EditorHelp")); theme_cache.override_color = get_theme_color(SNAME("override_color"), SNAME("EditorHelp")); + theme_cache.primary_hr_color = Color(theme_cache.title_color, 0.25); + theme_cache.secondary_hr_color = Color(theme_cache.comment_color, 0.25); theme_cache.doc_font = get_theme_font(SNAME("doc"), EditorStringName(EditorFonts)); theme_cache.doc_bold_font = get_theme_font(SNAME("doc_bold"), EditorStringName(EditorFonts)); @@ -225,14 +236,17 @@ void EditorHelp::_update_theme_item_cache() { theme_cache.background_style = get_theme_stylebox(SNAME("background"), SNAME("EditorHelp")); class_desc->begin_bulk_theme_override(); + 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_constant_override(SceneStringName(line_separation), get_theme_constant(SceneStringName(line_separation), SNAME("EditorHelp"))); + class_desc->add_theme_constant_override(SceneStringName(paragraph_separation), get_theme_constant(SceneStringName(paragraph_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"))); class_desc->add_theme_constant_override("text_highlight_h_padding", get_theme_constant(SNAME("text_highlight_h_padding"), SNAME("EditorHelp"))); class_desc->add_theme_constant_override("text_highlight_v_padding", get_theme_constant(SNAME("text_highlight_v_padding"), SNAME("EditorHelp"))); + class_desc->end_bulk_theme_override(); } @@ -738,10 +752,8 @@ void EditorHelp::_update_method_list(MethodType p_method_type, const Vectoradd_text(title); _pop_title_font(); - class_desc->add_newline(); class_desc->add_newline(); - class_desc->push_indent(1); _push_code_font(); class_desc->push_table(2); class_desc->set_table_column_expand(1, true); @@ -793,14 +805,13 @@ void EditorHelp::_update_method_list(MethodType p_method_type, const Vectorpop(); // table _pop_code_font(); - class_desc->pop(); // indent } void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc, MethodType p_method_type, const Vector &p_methods) { #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(); + class_desc->add_hr(100, 2, theme_cache.primary_hr_color); class_desc->add_newline(); static const char *titles_by_type[METHOD_TYPE_MAX] = { @@ -817,29 +828,33 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc String link_color_text = theme_cache.title_color.to_html(false); + bool is_first_method = true; for (int pass = 0; pass < 2; pass++) { - Vector methods_filtered; + Vector methods_filtered; - for (int i = 0; i < p_methods.size(); i++) { - const String &q = p_methods[i].qualifiers; + for (const DocData::MethodDoc &method : p_methods) { + const String &q = method.qualifiers; if ((pass == 0 && q.contains("virtual")) || (pass == 1 && !q.contains("virtual"))) { - methods_filtered.push_back(p_methods[i]); + methods_filtered.push_back(&method); } } for (int i = 0; i < methods_filtered.size(); i++) { - const DocData::MethodDoc &method = methods_filtered[i]; + const DocData::MethodDoc &method = *methods_filtered[i]; class_desc->add_newline(); - class_desc->add_newline(); - class_desc->add_newline(); + if (is_first_method) { + is_first_method = false; + } else { + class_desc->add_hr(100, 1, theme_cache.secondary_hr_color); + class_desc->add_newline(); + } _push_code_font(); // For constructors always point to the first one. _add_method(method, false, (p_method_type != METHOD_TYPE_CONSTRUCTOR || i == 0)); _pop_code_font(); - class_desc->add_newline(); class_desc->add_newline(); class_desc->push_indent(1); @@ -862,7 +877,6 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc if (method.is_experimental) { if (has_prev_text) { class_desc->add_newline(); - class_desc->add_newline(); } has_prev_text = true; @@ -877,7 +891,6 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc if (!method.errors_returned.is_empty()) { if (has_prev_text) { class_desc->add_newline(); - class_desc->add_newline(); } has_prev_text = true; @@ -910,7 +923,6 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc if (!descr.is_empty()) { if (has_prev_text) { class_desc->add_newline(); - class_desc->add_newline(); } has_prev_text = true; @@ -918,7 +930,6 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc &p_classdoc } else if (!is_documented) { if (has_prev_text) { class_desc->add_newline(); - class_desc->add_newline(); } has_prev_text = true; @@ -1032,7 +1043,7 @@ void EditorHelp::_update_doc() { _push_normal_font(); class_desc->push_color(theme_cache.title_color); - class_desc->add_text(TTR("Inherited by:") + " "); + class_desc->add_text(TTR("Inherited By:") + " "); for (RBSet::Element *itr = doc->inheriting[cd.name].front(); itr; itr = itr->next()) { if (itr->prev()) { @@ -1048,60 +1059,45 @@ void EditorHelp::_update_doc() { _pop_normal_font(); } - bool has_description = false; - - // Brief description - const String brief_class_descr = HANDLE_DOC(cd.brief_description); - if (!brief_class_descr.is_empty()) { - has_description = true; - - class_desc->add_newline(); - class_desc->add_newline(); - - class_desc->push_indent(1); - class_desc->push_font(theme_cache.doc_bold_font); - class_desc->push_color(theme_cache.text_color); - - _add_text(brief_class_descr); - - class_desc->pop(); // color - class_desc->pop(); // font - class_desc->pop(); // indent - } - // Class description + class_desc->add_newline(); + class_desc->add_newline(); + + section_line.push_back(Pair(TTR("Description"), class_desc->get_paragraph_count() - 2)); + description_line = class_desc->get_paragraph_count() - 2; + _push_title_font(); + class_desc->add_text(TTR("Description")); + _pop_title_font(); + + const String brief_class_descr = HANDLE_DOC(cd.brief_description); const String class_descr = HANDLE_DOC(cd.description); - if (!class_descr.is_empty()) { - has_description = true; + if (!brief_class_descr.is_empty() || !class_descr.is_empty()) { + if (!brief_class_descr.is_empty()) { + class_desc->add_newline(); - class_desc->add_newline(); + class_desc->push_font(theme_cache.doc_bold_font); + class_desc->push_color(theme_cache.text_color); + + _add_text(brief_class_descr); + + class_desc->pop(); // color + class_desc->pop(); // font + } + + if (!class_descr.is_empty()) { + class_desc->add_newline(); + + _push_normal_font(); + class_desc->push_color(theme_cache.text_color); + + _add_text(class_descr); + + class_desc->pop(); // color + _pop_normal_font(); + } + } else { class_desc->add_newline(); - section_line.push_back(Pair(TTR("Description"), class_desc->get_paragraph_count() - 2)); - description_line = class_desc->get_paragraph_count() - 2; - _push_title_font(); - class_desc->add_text(TTR("Description")); - _pop_title_font(); - - class_desc->add_newline(); - class_desc->add_newline(); - - class_desc->push_indent(1); - _push_normal_font(); - class_desc->push_color(theme_cache.text_color); - - _add_text(class_descr); - - class_desc->pop(); // color - _pop_normal_font(); - class_desc->pop(); // indent - } - - if (!has_description) { - class_desc->add_newline(); - class_desc->add_newline(); - - class_desc->push_indent(1); _push_normal_font(); class_desc->add_image(get_editor_theme_icon(SNAME("Error"))); @@ -1116,17 +1112,14 @@ void EditorHelp::_update_doc() { class_desc->pop(); // color _pop_normal_font(); - class_desc->pop(); // indent } #ifdef MODULE_MONO_ENABLED if (classes_with_csharp_differences.has(cd.name)) { class_desc->add_newline(); - class_desc->add_newline(); const String &csharp_differences_url = vformat("%s/tutorials/scripting/c_sharp/c_sharp_differences.html", GODOT_VERSION_DOCS_URL); - class_desc->push_indent(1); _push_normal_font(); class_desc->push_color(theme_cache.text_color); @@ -1134,7 +1127,6 @@ void EditorHelp::_update_doc() { class_desc->pop(); // color _pop_normal_font(); - class_desc->pop(); // indent } #endif @@ -1143,6 +1135,8 @@ void EditorHelp::_update_doc() { class_desc->add_newline(); class_desc->add_newline(); + section_line.push_back(Pair(TTR("Online Tutorials"), class_desc->get_paragraph_count() - 2)); + description_line = class_desc->get_paragraph_count() - 2; _push_title_font(); class_desc->add_text(TTR("Online Tutorials")); _pop_title_font(); @@ -1153,6 +1147,7 @@ void EditorHelp::_update_doc() { _push_code_font(); class_desc->push_color(theme_cache.symbol_color); + bool is_first_tutorial = true; for (const DocData::TutorialDoc &tutorial : cd.tutorials) { const String link = HANDLE_DOC(tutorial.link); @@ -1166,7 +1161,13 @@ void EditorHelp::_update_doc() { } } - class_desc->add_newline(); + if (is_first_tutorial) { + is_first_tutorial = false; + } else { + // `\n` starts a new paragraph, `\r` just adds a break to existing one. + class_desc->add_text("\r"); + } + _add_bulletpoint(); class_desc->append_text("[url=" + link + "]" + link_text + "[/url]"); } @@ -1202,10 +1203,8 @@ void EditorHelp::_update_doc() { class_desc->add_text(TTR("Properties")); _pop_title_font(); - class_desc->add_newline(); class_desc->add_newline(); - class_desc->push_indent(1); _push_code_font(); class_desc->push_table(4); class_desc->set_table_column_expand(1, true); @@ -1365,7 +1364,6 @@ void EditorHelp::_update_doc() { class_desc->pop(); // table _pop_code_font(); - class_desc->pop(); // indent } // Methods overview @@ -1411,6 +1409,7 @@ void EditorHelp::_update_doc() { // Theme properties if (!cd.theme_properties.is_empty()) { class_desc->add_newline(); + class_desc->add_hr(100, 2, theme_cache.primary_hr_color); class_desc->add_newline(); section_line.push_back(Pair(TTR("Theme Properties"), class_desc->get_paragraph_count() - 2)); @@ -1431,7 +1430,6 @@ void EditorHelp::_update_doc() { if (theme_data_type != theme_item.data_type) { theme_data_type = theme_item.data_type; - class_desc->add_newline(); class_desc->add_newline(); class_desc->push_indent(1); @@ -1447,7 +1445,6 @@ void EditorHelp::_update_doc() { class_desc->pop(); // indent } - class_desc->add_newline(); class_desc->add_newline(); theme_property_line[theme_item.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description. @@ -1499,7 +1496,6 @@ void EditorHelp::_update_doc() { if (theme_item.is_experimental) { if (has_prev_text) { class_desc->add_newline(); - class_desc->add_newline(); } has_prev_text = true; EXPERIMENTAL_DOC_MSG(HANDLE_DOC(theme_item.experimental_message), TTR("This theme property may be changed or removed in future versions.")); @@ -1509,7 +1505,6 @@ void EditorHelp::_update_doc() { if (!descr.is_empty()) { if (has_prev_text) { class_desc->add_newline(); - class_desc->add_newline(); } has_prev_text = true; _add_text(descr); @@ -1540,7 +1535,7 @@ void EditorHelp::_update_doc() { } bool header_added = false; - + bool is_first_signal = true; for (const DocData::MethodDoc &signal : cd.signals) { // Ignore undocumented private. const bool is_documented = signal.is_deprecated || signal.is_experimental || !signal.description.strip_edges().is_empty(); @@ -1552,6 +1547,7 @@ void EditorHelp::_update_doc() { header_added = true; class_desc->add_newline(); + class_desc->add_hr(100, 2, theme_cache.primary_hr_color); class_desc->add_newline(); section_line.push_back(Pair(TTR("Signals"), class_desc->get_paragraph_count() - 2)); @@ -1561,12 +1557,15 @@ void EditorHelp::_update_doc() { } class_desc->add_newline(); - class_desc->add_newline(); + if (is_first_signal) { + is_first_signal = false; + } else { + class_desc->add_hr(100, 1, theme_cache.secondary_hr_color); + class_desc->add_newline(); + } signal_line[signal.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description. - class_desc->push_indent(1); - // Signal header. _push_code_font(); _add_bulletpoint(); @@ -1624,7 +1623,6 @@ void EditorHelp::_update_doc() { class_desc->push_color(theme_cache.comment_color); 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) { @@ -1635,9 +1633,6 @@ void EditorHelp::_update_doc() { if (signal.is_experimental) { if (has_prev_text) { class_desc->add_newline(); - if (is_multiline) { - class_desc->add_newline(); - } } has_prev_text = true; EXPERIMENTAL_DOC_MSG(HANDLE_DOC(signal.experimental_message), TTR("This signal may be changed or removed in future versions.")); @@ -1646,9 +1641,6 @@ void EditorHelp::_update_doc() { if (!descr.is_empty()) { if (has_prev_text) { class_desc->add_newline(); - if (is_multiline) { - class_desc->add_newline(); - } } has_prev_text = true; _add_text(descr); @@ -1667,8 +1659,6 @@ void EditorHelp::_update_doc() { class_desc->pop(); // color _pop_normal_font(); class_desc->pop(); // indent - - class_desc->pop(); // indent } } @@ -1708,6 +1698,7 @@ void EditorHelp::_update_doc() { } if (has_enums) { class_desc->add_newline(); + class_desc->add_hr(100, 2, theme_cache.primary_hr_color); class_desc->add_newline(); section_line.push_back(Pair(TTR("Enumerations"), class_desc->get_paragraph_count() - 2)); @@ -1715,6 +1706,7 @@ void EditorHelp::_update_doc() { class_desc->add_text(TTR("Enumerations")); _pop_title_font(); + bool is_first_enum = true; for (KeyValue> &E : enums) { String key = E.key; if ((key.get_slice_count(".") > 1) && (key.get_slicec('.', 0) == edited_class)) { @@ -1728,7 +1720,12 @@ void EditorHelp::_update_doc() { } class_desc->add_newline(); - class_desc->add_newline(); + if (is_first_enum) { + is_first_enum = false; + } else { + class_desc->add_hr(100, 1, theme_cache.secondary_hr_color); + class_desc->add_newline(); + } // Enum header. _push_code_font(); @@ -1755,7 +1752,6 @@ void EditorHelp::_update_doc() { // Enum description. if (key != "@unnamed_enums" && cd.enums.has(key)) { 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(); @@ -1773,9 +1769,6 @@ void EditorHelp::_update_doc() { if (cd.enums[key].is_experimental) { if (has_prev_text) { class_desc->add_newline(); - if (is_multiline) { - class_desc->add_newline(); - } } has_prev_text = true; EXPERIMENTAL_DOC_MSG(HANDLE_DOC(cd.enums[key].experimental_message), TTR("This enumeration may be changed or removed in future versions.")); @@ -1784,9 +1777,6 @@ void EditorHelp::_update_doc() { if (!descr.is_empty()) { if (has_prev_text) { class_desc->add_newline(); - if (is_multiline) { - class_desc->add_newline(); - } } has_prev_text = true; _add_text(descr); @@ -1801,16 +1791,10 @@ void EditorHelp::_update_doc() { HashMap enum_values; const int enum_start_line = enum_line[E.key]; - bool prev_is_multiline = true; // Use a large margin for the first item. for (const DocData::ConstantDoc &enum_value : E.value) { const String descr = HANDLE_DOC(enum_value.description); - const bool is_multiline = descr.find_char('\n') > 0; class_desc->add_newline(); - if (prev_is_multiline || is_multiline) { - class_desc->add_newline(); - } - prev_is_multiline = is_multiline; if (cd.name == "@GlobalScope") { enum_values[enum_value.name] = enum_start_line; @@ -1857,9 +1841,6 @@ void EditorHelp::_update_doc() { if (enum_value.is_experimental) { if (has_prev_text) { class_desc->add_newline(); - if (is_multiline) { - class_desc->add_newline(); - } } has_prev_text = true; EXPERIMENTAL_DOC_MSG(HANDLE_DOC(enum_value.experimental_message), TTR("This constant may be changed or removed in future versions.")); @@ -1868,9 +1849,6 @@ void EditorHelp::_update_doc() { if (!descr.is_empty()) { if (has_prev_text) { class_desc->add_newline(); - if (is_multiline) { - class_desc->add_newline(); - } } has_prev_text = true; _add_text(descr); @@ -1893,6 +1871,7 @@ void EditorHelp::_update_doc() { // Constants if (!constants.is_empty()) { class_desc->add_newline(); + class_desc->add_hr(100, 2, theme_cache.primary_hr_color); class_desc->add_newline(); section_line.push_back(Pair(TTR("Constants"), class_desc->get_paragraph_count() - 2)); @@ -1900,21 +1879,20 @@ void EditorHelp::_update_doc() { class_desc->add_text(TTR("Constants")); _pop_title_font(); - bool prev_is_multiline = true; // Use a large margin for the first item. + bool is_first_constant = true; for (const DocData::ConstantDoc &constant : constants) { const String descr = HANDLE_DOC(constant.description); - const bool is_multiline = descr.find_char('\n') > 0; class_desc->add_newline(); - if (prev_is_multiline || is_multiline) { + if (is_first_constant) { + is_first_constant = false; + } else { + class_desc->add_hr(100, 1, theme_cache.secondary_hr_color); class_desc->add_newline(); } - prev_is_multiline = is_multiline; constant_line[constant.name] = class_desc->get_paragraph_count() - 2; - class_desc->push_indent(1); - // Constant header. _push_code_font(); @@ -1962,9 +1940,6 @@ void EditorHelp::_update_doc() { if (constant.is_experimental) { if (has_prev_text) { class_desc->add_newline(); - if (is_multiline) { - class_desc->add_newline(); - } } has_prev_text = true; EXPERIMENTAL_DOC_MSG(HANDLE_DOC(constant.experimental_message), TTR("This constant may be changed or removed in future versions.")); @@ -1973,9 +1948,6 @@ void EditorHelp::_update_doc() { if (!descr.is_empty()) { if (has_prev_text) { class_desc->add_newline(); - if (is_multiline) { - class_desc->add_newline(); - } } has_prev_text = true; _add_text(descr); @@ -1985,8 +1957,6 @@ void EditorHelp::_update_doc() { _pop_normal_font(); class_desc->pop(); // indent } - - class_desc->pop(); // indent } } } @@ -1998,6 +1968,7 @@ void EditorHelp::_update_doc() { } class_desc->add_newline(); + class_desc->add_hr(100, 2, theme_cache.primary_hr_color); class_desc->add_newline(); section_line.push_back(Pair(TTR("Annotations"), class_desc->get_paragraph_count() - 2)); @@ -2005,14 +1976,18 @@ void EditorHelp::_update_doc() { class_desc->add_text(TTR("Annotations")); _pop_title_font(); + bool is_first_annotation = true; for (const DocData::MethodDoc &annotation : cd.annotations) { class_desc->add_newline(); - class_desc->add_newline(); + if (is_first_annotation) { + is_first_annotation = false; + } else { + class_desc->add_hr(100, 1, theme_cache.secondary_hr_color); + class_desc->add_newline(); + } annotation_line[annotation.name] = class_desc->get_paragraph_count() - 2; // Gets overridden if description. - class_desc->push_indent(1); - // Annotation header. _push_code_font(); _add_bulletpoint(); @@ -2122,15 +2097,13 @@ void EditorHelp::_update_doc() { class_desc->pop(); // color _pop_normal_font(); class_desc->pop(); // indent - - class_desc->pop(); // indent } } // Property descriptions if (has_property_descriptions) { class_desc->add_newline(); - class_desc->add_newline(); + class_desc->add_hr(100, 2, theme_cache.primary_hr_color); class_desc->add_newline(); section_line.push_back(Pair(TTR("Property Descriptions"), class_desc->get_paragraph_count() - 2)); @@ -2138,6 +2111,7 @@ void EditorHelp::_update_doc() { class_desc->add_text(TTR("Property Descriptions")); _pop_title_font(); + bool is_first_property = true; for (const DocData::PropertyDoc &prop : cd.properties) { if (prop.overridden) { continue; @@ -2149,8 +2123,12 @@ void EditorHelp::_update_doc() { } class_desc->add_newline(); - class_desc->add_newline(); - class_desc->add_newline(); + if (is_first_property) { + is_first_property = false; + } else { + class_desc->add_hr(100, 1, theme_cache.secondary_hr_color); + class_desc->add_newline(); + } property_line[prop.name] = class_desc->get_paragraph_count() - 2; @@ -2278,7 +2256,6 @@ void EditorHelp::_update_doc() { class_desc->pop(); // table - class_desc->add_newline(); class_desc->add_newline(); class_desc->push_indent(1); @@ -2295,7 +2272,6 @@ void EditorHelp::_update_doc() { if (prop.is_experimental) { if (has_prev_text) { class_desc->add_newline(); - class_desc->add_newline(); } has_prev_text = true; EXPERIMENTAL_DOC_MSG(HANDLE_DOC(prop.experimental_message), TTR("This property may be changed or removed in future versions.")); @@ -2305,7 +2281,6 @@ void EditorHelp::_update_doc() { if (!descr.is_empty()) { if (has_prev_text) { class_desc->add_newline(); - class_desc->add_newline(); } has_prev_text = true; _add_text(descr); @@ -2323,7 +2298,6 @@ void EditorHelp::_update_doc() { // Add copy note to built-in properties returning `Packed*Array`. if (!cd.is_script_doc && packed_array_types.has(prop.type)) { - class_desc->add_newline(); class_desc->add_newline(); // See also `EditorHelpBit::parse_symbol()` and `doc/tools/make_rst.py`. _add_text(vformat(TTR("[b]Note:[/b] The returned array is [i]copied[/i] and any changes to it will not update the original property value. See [%s] for more details."), prop.type)); @@ -2352,7 +2326,6 @@ void EditorHelp::_update_doc() { // Allow the document to be scrolled slightly below the end. class_desc->add_newline(); - class_desc->add_newline(); // Free the scroll. scroll_locked = false; @@ -2544,7 +2517,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const C bbcode = bbcode.replace("[codeblocks]", ""); bbcode = bbcode.replace("[/codeblocks]", ""); - // Remove `\n` here because `\n` is replaced by `\n\n` later. + // Remove `\n` here because it doesn't look nice. // Will be compensated when parsing `[/codeblock]`. bbcode = bbcode.replace("[/codeblock]\n", "[/codeblock]"); @@ -2559,7 +2532,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const C } if (brk_pos > pos) { - p_rt->add_text(bbcode.substr(pos, brk_pos - pos).replace("\n", "\n\n")); + p_rt->add_text(bbcode.substr(pos, brk_pos - pos)); } if (brk_pos == bbcode.length()) { @@ -2569,7 +2542,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const C int brk_end = bbcode.find_char(']', brk_pos + 1); if (brk_end == -1) { - p_rt->add_text(bbcode.substr(brk_pos).replace("\n", "\n\n")); + p_rt->add_text(bbcode.substr(brk_pos)); break; } @@ -2714,7 +2687,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const C p_rt->push_bgcolor(code_bg_color); p_rt->push_color(code_color.lerp(p_owner_node->get_theme_color(SNAME("error_color"), EditorStringName(Editor)), 0.6)); - p_rt->add_text(bbcode.substr(brk_end + 1, end_pos - (brk_end + 1))); + p_rt->add_text(_fix_newlines(bbcode.substr(brk_end + 1, end_pos - (brk_end + 1)))); p_rt->pop(); // color p_rt->pop(); // bgcolor @@ -2789,7 +2762,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const C #endif if (!codeblock_printed) { - p_rt->add_text(codeblock_text); + p_rt->add_text(_fix_newlines(codeblock_text)); codeblock_printed = true; } @@ -2814,7 +2787,8 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const C // Compensate for `\n` removed before the loop. if (pos < bbcode.length()) { - p_rt->add_newline(); + // `\n` starts a new paragraph, `\r` just adds a break to existing one. + p_rt->add_text("\r"); } } else if (tag == "kbd") { int end_pos = bbcode.find("[/kbd]", brk_end + 1); @@ -2828,7 +2802,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const C p_rt->push_bgcolor(kbd_bg_color); p_rt->push_color(kbd_color); - p_rt->add_text(bbcode.substr(brk_end + 1, end_pos - (brk_end + 1))); + p_rt->add_text(_fix_newlines(bbcode.substr(brk_end + 1, end_pos - (brk_end + 1)))); p_rt->pop(); // color p_rt->pop(); // bgcolor @@ -2842,8 +2816,8 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, const C pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag == "br") { - // Force a line break. - p_rt->add_newline(); + // `\n` starts a new paragraph, `\r` just adds a break to existing one. + p_rt->add_text("\r"); pos = brk_end + 1; } else if (tag == "u") { // Use underline. @@ -3449,7 +3423,9 @@ EditorHelp::EditorHelp() { class_desc = memnew(RichTextLabel); class_desc->set_tab_size(8); + class_desc->set_autowrap_trim_flags(TextServer::BREAK_TRIM_END_EDGE_SPACES); add_child(class_desc); + class_desc->set_threaded(true); class_desc->set_v_size_flags(SIZE_EXPAND_FILL); @@ -3478,7 +3454,7 @@ EditorHelp::EditorHelp() { class_desc->set_selection_enabled(true); class_desc->set_context_menu_enabled(true); - class_desc->set_selection_modifier(callable_mp_static(_replace_nbsp_with_space)); + class_desc->set_selection_modifier(callable_mp_static(_fix_selection)); class_desc->hide(); } @@ -4155,7 +4131,6 @@ void EditorHelpBit::_update_labels() { if (!help_data.experimental_message.is_empty()) { if (has_prev_text) { content->add_newline(); - content->add_newline(); } has_prev_text = true; @@ -4174,7 +4149,6 @@ void EditorHelpBit::_update_labels() { if (!help_data.description.is_empty()) { if (has_prev_text) { content->add_newline(); - content->add_newline(); } has_prev_text = true; @@ -4185,7 +4159,6 @@ void EditorHelpBit::_update_labels() { if (!help_data.resource_path.is_empty()) { if (has_prev_text) { content->add_newline(); - content->add_newline(); } has_prev_text = true; @@ -4369,6 +4342,17 @@ void EditorHelpBit::_bind_methods() { void EditorHelpBit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: + content->begin_bulk_theme_override(); + + //content->add_theme_constant_override(SceneStringName(line_separation), get_theme_constant(SceneStringName(line_separation), SNAME("EditorHelp"))); + content->add_theme_constant_override(SceneStringName(paragraph_separation), get_theme_constant(SceneStringName(paragraph_separation), SNAME("EditorHelp"))); + content->add_theme_constant_override("table_h_separation", get_theme_constant(SNAME("table_h_separation"), SNAME("EditorHelp"))); + content->add_theme_constant_override("table_v_separation", get_theme_constant(SNAME("table_v_separation"), SNAME("EditorHelp"))); + content->add_theme_constant_override("text_highlight_h_padding", get_theme_constant(SNAME("text_highlight_h_padding"), SNAME("EditorHelp"))); + content->add_theme_constant_override("text_highlight_v_padding", get_theme_constant(SNAME("text_highlight_v_padding"), SNAME("EditorHelp"))); + + content->end_bulk_theme_override(); + _update_labels(); break; } @@ -4597,7 +4581,7 @@ EditorHelpBit::EditorHelpBit(const String &p_symbol, const String &p_prologue, b title->set_fit_content(true); title->set_selection_enabled(p_allow_selection); title->set_context_menu_enabled(p_allow_selection); - title->set_selection_modifier(callable_mp_static(_replace_nbsp_with_space)); + title->set_selection_modifier(callable_mp_static(_fix_selection)); title->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); title->hide(); add_child(title); @@ -4607,11 +4591,12 @@ EditorHelpBit::EditorHelpBit(const String &p_symbol, const String &p_prologue, b content = memnew(RichTextLabel); content->set_theme_type_variation(p_in_tooltip ? "EditorHelpBitTooltipContent" : "EditorHelpBitContent"); + content->set_autowrap_trim_flags(TextServer::BREAK_TRIM_END_EDGE_SPACES); content->set_custom_minimum_size(Size2(640 * EDSCALE, content_min_height)); content->set_v_size_flags(Control::SIZE_EXPAND_FILL); content->set_selection_enabled(p_allow_selection); content->set_context_menu_enabled(p_allow_selection); - content->set_selection_modifier(callable_mp_static(_replace_nbsp_with_space)); + content->set_selection_modifier(callable_mp_static(_fix_selection)); content->connect("meta_clicked", callable_mp(this, &EditorHelpBit::_meta_clicked)); add_child(content); @@ -4872,7 +4857,7 @@ void EditorHelpHighlighter::highlight(RichTextLabel *p_rich_text_label, Language const Pair &prev = highlight_data[i - 1]; const Pair &curr = highlight_data[i]; p_rich_text_label->push_color(prev.second); - p_rich_text_label->add_text(p_source.substr(prev.first, curr.first - prev.first)); + p_rich_text_label->add_text(_fix_newlines(p_source.substr(prev.first, curr.first - prev.first))); p_rich_text_label->pop(); // color } diff --git a/editor/doc/editor_help.h b/editor/doc/editor_help.h index b90e012d1ef..f72246ed226 100644 --- a/editor/doc/editor_help.h +++ b/editor/doc/editor_help.h @@ -132,6 +132,8 @@ class EditorHelp : public VBoxContainer { Color qualifier_color; Color type_color; Color override_color; + Color primary_hr_color; + Color secondary_hr_color; Ref doc_font; Ref doc_bold_font; diff --git a/editor/themes/theme_classic.cpp b/editor/themes/theme_classic.cpp index 85c20741e3a..5df3b8efbc6 100644 --- a/editor/themes/theme_classic.cpp +++ b/editor/themes/theme_classic.cpp @@ -2330,6 +2330,7 @@ void ThemeClassic::populate_editor_styles(const Ref &p_theme, Edito p_theme->set_color("kbd_bg_color", "EditorHelp", p_config.dark_color_1); p_theme->set_color("param_bg_color", "EditorHelp", p_config.dark_color_1); p_theme->set_constant(SceneStringName(line_separation), "EditorHelp", Math::round(6 * EDSCALE)); + p_theme->set_constant(SceneStringName(paragraph_separation), "EditorHelp", Math::round(10 * EDSCALE)); p_theme->set_constant("table_h_separation", "EditorHelp", 16 * EDSCALE); p_theme->set_constant("table_v_separation", "EditorHelp", 6 * EDSCALE); p_theme->set_constant("text_highlight_h_padding", "EditorHelp", 1 * EDSCALE); diff --git a/editor/themes/theme_modern.cpp b/editor/themes/theme_modern.cpp index 16803b01e2c..ff9e5f7fab5 100644 --- a/editor/themes/theme_modern.cpp +++ b/editor/themes/theme_modern.cpp @@ -2644,6 +2644,7 @@ void ThemeModern::populate_editor_styles(const Ref &p_theme, Editor p_theme->set_color("kbd_bg_color", "EditorHelp", p_config.dark_color_1); p_theme->set_color("param_bg_color", "EditorHelp", p_config.dark_color_1); p_theme->set_constant(SceneStringName(line_separation), "EditorHelp", Math::round(6 * EDSCALE)); + p_theme->set_constant(SceneStringName(paragraph_separation), "EditorHelp", Math::round(10 * EDSCALE)); p_theme->set_constant("table_h_separation", "EditorHelp", 16 * EDSCALE); p_theme->set_constant("table_v_separation", "EditorHelp", 6 * EDSCALE); p_theme->set_constant("text_highlight_h_padding", "EditorHelp", 1 * EDSCALE); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index ae245763722..5e874814d08 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -3918,20 +3918,24 @@ enum DocLineState { DOC_LINE_IN_KBD, }; -static String _process_doc_line(const String &p_line, const String &p_text, const String &p_space_prefix, DocLineState &r_state) { +static void _process_doc_line(const String &p_line, String &r_text, const String &p_space_prefix, DocLineState &r_state) { String line = p_line; if (r_state == DOC_LINE_NORMAL) { - line = line.strip_edges(true, false); + line = line.lstrip(" \t"); } else { line = line.trim_prefix(p_space_prefix); } String line_join; - if (!p_text.is_empty()) { + if (!r_text.is_empty()) { if (r_state == DOC_LINE_NORMAL) { - if (p_text.ends_with("[/codeblock]")) { + if (r_text.ends_with("[/codeblock]")) { line_join = "\n"; - } else if (!p_text.ends_with("[br]")) { + } else if (r_text.ends_with("[br]")) { + // We want to replace `[br][br]` with `\n` (paragraph), so we move the trailing `[br]` here. + r_text = r_text.left(-4); // `-len("[br]")`. + line = "[br]" + line; + } else if (!r_text.ends_with("\n")) { line_join = " "; } } else { @@ -3961,7 +3965,14 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons from = rb_pos + 1; String tag = line.substr(lb_pos + 1, rb_pos - lb_pos - 1); - if (tag == "code" || tag.begins_with("code ")) { + if (tag == "br") { + if (line.substr(from, 4) == "[br]") { // `len("[br]")`. + // Replace `[br][br]` with `\n` (paragraph). + result += line.substr(buffer_start, lb_pos - buffer_start) + '\n'; + from += 4; // `len("[br]")`. + buffer_start = from; + } + } else if (tag == "code" || tag.begins_with("code ")) { r_state = DOC_LINE_IN_CODE; } else if (tag == "codeblock" || tag.begins_with("codeblock ")) { if (lb_pos == 0) { @@ -4029,10 +4040,10 @@ static String _process_doc_line(const String &p_line, const String &p_text, cons result += line.substr(buffer_start); if (r_state == DOC_LINE_NORMAL) { - result = result.strip_edges(false, true); + result = result.rstrip(" \t"); } - return line_join + result; + r_text += line_join + result; } bool GDScriptParser::has_comment(int p_line, bool p_must_be_doc) { @@ -4095,7 +4106,7 @@ GDScriptParser::MemberDocData GDScriptParser::parse_doc_comment(int p_line, bool } } - result.description += _process_doc_line(doc_line, result.description, space_prefix, state); + _process_doc_line(doc_line, result.description, space_prefix, state); } return result; @@ -4205,9 +4216,9 @@ GDScriptParser::ClassDocData GDScriptParser::parse_class_doc_comment(int p_line, } if (is_in_brief) { - result.brief += _process_doc_line(doc_line, result.brief, space_prefix, state); + _process_doc_line(doc_line, result.brief, space_prefix, state); } else { - result.description += _process_doc_line(doc_line, result.description, space_prefix, state); + _process_doc_line(doc_line, result.description, space_prefix, state); } } diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 412eea261bb..7f051e89b10 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -5737,6 +5737,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("lang"); } else if (tag == "br") { + // `\n` starts a new paragraph, `\r` just adds a break to existing one. add_text("\r"); pos = brk_end + 1; } else if (tag == "p") {