1
0
mirror of https://github.com/godotengine/godot.git synced 2025-11-04 12:00:25 +00:00

Improve EditorAbout dialog auto-translation

This commit is contained in:
Haoyu Qiu
2025-04-12 12:41:33 +08:00
parent 1b4ed4c038
commit c5a70dbeeb
3 changed files with 156 additions and 156 deletions

View File

@@ -51,6 +51,7 @@ Label *CreditsRoll::_create_label(const String &p_with_text, LabelSize p_size) {
switch (p_size) { switch (p_size) {
case LabelSize::NORMAL: { case LabelSize::NORMAL: {
label->add_theme_font_size_override(SceneStringName(font_size), font_size_normal); label->add_theme_font_size_override(SceneStringName(font_size), font_size_normal);
label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
} break; } break;
case LabelSize::HEADER: { case LabelSize::HEADER: {
@@ -165,6 +166,7 @@ void CreditsRoll::roll_credits() {
_create_nothing(); _create_nothing();
project_manager = _create_label(TTR("Project Manager", "Job Title"), LabelSize::HEADER); project_manager = _create_label(TTR("Project Manager", "Job Title"), LabelSize::HEADER);
project_manager->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
_create_label(_build_string(AUTHORS_PROJECT_MANAGERS)); _create_label(_build_string(AUTHORS_PROJECT_MANAGERS));
_create_nothing(); _create_nothing();

View File

@@ -39,13 +39,34 @@
#include "editor/gui/editor_version_button.h" #include "editor/gui/editor_version_button.h"
#include "editor/themes/editor_scale.h" #include "editor/themes/editor_scale.h"
#include "scene/gui/item_list.h" #include "scene/gui/item_list.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/scroll_container.h"
#include "scene/gui/separator.h" #include "scene/gui/separator.h"
#include "scene/gui/split_container.h" #include "scene/gui/split_container.h"
#include "scene/gui/tab_container.h" #include "scene/gui/tab_container.h"
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
#include "scene/resources/style_box.h" #include "scene/resources/style_box.h"
void EditorAbout::_notification(int p_what) { void EditorAbout::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_TRANSLATION_CHANGED: {
_about_text_label->set_text(
String(U"© 2014-present ") + TTR("Godot Engine contributors") + ".\n" +
String(U"© 2007-2014 Juan Linietsky, Ariel Manzur.\n"));
_project_manager_label->set_text(TTR("Project Manager", "Job Title"));
for (ItemList *il : name_lists) {
for (int i = 0; i < il->get_item_count(); i++) {
const Variant val = il->get_item_metadata(i);
if (val.get_type() == Variant::STRING) {
il->set_item_tooltip(i, val.operator String() + "\n\n" + TTR("Double-click to open in browser."));
}
}
}
} break;
case NOTIFICATION_THEME_CHANGED: { case NOTIFICATION_THEME_CHANGED: {
const Ref<Font> font = get_theme_font(SNAME("source"), EditorStringName(EditorFonts)); const Ref<Font> font = get_theme_font(SNAME("source"), EditorStringName(EditorFonts));
const int font_size = get_theme_font_size(SNAME("source_size"), EditorStringName(EditorFonts)); const int font_size = get_theme_font_size(SNAME("source_size"), EditorStringName(EditorFonts));
@@ -82,23 +103,22 @@ void EditorAbout::_license_tree_selected() {
_tpl_text->set_text(selected->get_metadata(0)); _tpl_text->set_text(selected->get_metadata(0));
} }
void EditorAbout::_project_manager_clicked() { void EditorAbout::_item_activated(int p_idx, ItemList *p_il) {
if (!EditorNode::get_singleton()) { const Variant val = p_il->get_item_metadata(p_idx);
// Don't allow in Project Manager. if (val.get_type() == Variant::STRING) {
return; OS::get_singleton()->shell_open(val);
} } else {
// Easter egg :D
if (!EditorNode::get_singleton()) {
// Don't allow in Project Manager.
return;
}
if (!credits_roll) { if (!credits_roll) {
credits_roll = memnew(CreditsRoll); credits_roll = memnew(CreditsRoll);
add_child(credits_roll); add_child(credits_roll);
} }
credits_roll->roll_credits(); credits_roll->roll_credits();
}
void EditorAbout::_item_with_website_selected(int p_id, ItemList *p_il) {
const String website = p_il->get_item_metadata(p_id);
if (!website.is_empty()) {
OS::get_singleton()->shell_open(website);
} }
} }
@@ -106,101 +126,73 @@ void EditorAbout::_item_list_resized(ItemList *p_il) {
p_il->set_fixed_column_width(p_il->get_size().x / 3.0 - 16 * EDSCALE * 2.5); // Weird. Should be 3.0 and that's it?. p_il->set_fixed_column_width(p_il->get_size().x / 3.0 - 16 * EDSCALE * 2.5); // Weird. Should be 3.0 and that's it?.
} }
ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], const int p_single_column_flags, const bool p_allow_website, const String &p_easter_egg_section) { Label *EditorAbout::_create_section(Control *p_parent, const String &p_name, const char *const *p_src, BitField<SectionFlags> p_flags) {
ScrollContainer *sc = memnew(ScrollContainer); Label *lbl = memnew(Label(p_name));
sc->set_name(p_name); lbl->set_theme_type_variation("HeaderSmall");
sc->set_v_size_flags(Control::SIZE_EXPAND); lbl->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
p_parent->add_child(lbl);
VBoxContainer *vbc = memnew(VBoxContainer); ItemList *il = memnew(ItemList);
vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); il->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
sc->add_child(vbc); il->set_h_size_flags(Control::SIZE_EXPAND_FILL);
il->set_same_column_width(true);
il->set_auto_height(true);
il->set_max_columns(p_flags.has_flag(FLAG_SINGLE_COLUMN) ? 1 : 16);
il->add_theme_constant_override("h_separation", 16 * EDSCALE);
Ref<StyleBoxEmpty> empty_stylebox = memnew(StyleBoxEmpty); if (p_flags.has_flag(FLAG_ALLOW_WEBSITE) || (p_flags.has_flag(FLAG_EASTER_EGG) && EditorNode::get_singleton())) {
Ref<StyleBoxEmpty> empty_stylebox = memnew(StyleBoxEmpty);
il->add_theme_style_override("focus", empty_stylebox);
il->add_theme_style_override("selected", empty_stylebox);
int i = 0; il->connect("item_activated", callable_mp(this, &EditorAbout::_item_activated).bind(il));
for (List<String>::ConstIterator itr = p_sections.begin(); itr != p_sections.end(); ++itr, ++i) { } else {
bool single_column = p_single_column_flags & (1 << i); il->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
const char *const *names_ptr = p_src[i]; il->set_focus_mode(Control::FOCUS_NONE);
if (*names_ptr) { }
const String &section_name = *itr;
Label *lbl = memnew(Label); const char *const *names_ptr = p_src;
lbl->set_focus_mode(Control::FOCUS_ACCESSIBILITY); if (p_flags.has_flag(FLAG_ALLOW_WEBSITE)) {
lbl->set_theme_type_variation("HeaderSmall"); il->connect(SceneStringName(resized), callable_mp(this, &EditorAbout::_item_list_resized).bind(il));
lbl->set_text(section_name); il->connect(SceneStringName(focus_exited), callable_mp(il, &ItemList::deselect_all));
vbc->add_child(lbl);
ItemList *il = memnew(ItemList); while (*names_ptr) {
il->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); const String name = String::utf8(*names_ptr++);
il->set_h_size_flags(Control::SIZE_EXPAND_FILL); const String identifier = name.get_slicec('<', 0);
il->set_same_column_width(true); const String website = name.get_slice_count("<") == 1 ? "" : name.get_slicec('<', 1).trim_suffix(">");
il->set_auto_height(true);
il->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
il->set_focus_mode(Control::FOCUS_NONE);
il->add_theme_constant_override("h_separation", 16 * EDSCALE);
if (p_allow_website) {
il->set_focus_mode(Control::FOCUS_CLICK);
il->set_mouse_filter(Control::MOUSE_FILTER_PASS);
il->connect("item_activated", callable_mp(this, &EditorAbout::_item_with_website_selected).bind(il)); il->add_item(identifier, nullptr, !website.is_empty());
il->connect(SceneStringName(resized), callable_mp(this, &EditorAbout::_item_list_resized).bind(il));
il->connect(SceneStringName(focus_exited), callable_mp(il, &ItemList::deselect_all));
il->add_theme_style_override("focus", empty_stylebox); if (website.is_empty()) {
il->add_theme_style_override("selected", empty_stylebox); il->set_item_tooltip_enabled(-1, false);
while (*names_ptr) {
const String name = String::utf8(*names_ptr++);
const String identifier = name.get_slicec('<', 0);
const String website = name.get_slice_count("<") == 1 ? "" : name.get_slicec('<', 1).trim_suffix(">");
const int name_item_id = il->add_item(identifier, nullptr, false);
il->set_item_tooltip_enabled(name_item_id, false);
if (!website.is_empty()) {
il->set_item_selectable(name_item_id, true);
il->set_item_metadata(name_item_id, website);
il->set_item_tooltip(name_item_id, website + "\n\n" + TTR("Double-click to open in browser."));
il->set_item_tooltip_enabled(name_item_id, true);
}
if (!*names_ptr && name.contains(" anonymous ")) {
il->set_item_disabled(name_item_id, true);
}
}
} else { } else {
if (section_name == p_easter_egg_section) { il->set_item_metadata(-1, website);
// Easter egg :D
il->set_focus_mode(Control::FOCUS_CLICK);
il->set_mouse_filter(Control::MOUSE_FILTER_PASS);
il->connect("item_activated", callable_mp(this, &EditorAbout::_project_manager_clicked).unbind(1), CONNECT_DEFERRED);
il->add_theme_style_override("focus", empty_stylebox);
il->add_theme_style_override("selected", empty_stylebox);
}
while (*names_ptr) {
il->add_item(String::utf8(*names_ptr++), nullptr, false);
}
} }
il->set_max_columns(single_column ? 1 : 16);
name_lists.append(il); if (!*names_ptr && name.contains(" anonymous ")) {
il->set_item_disabled(-1, true);
vbc->add_child(il); }
}
HSeparator *hs = memnew(HSeparator); } else {
hs->set_modulate(Color(0, 0, 0, 0)); while (*names_ptr) {
vbc->add_child(hs); il->add_item(String::utf8(*names_ptr++), nullptr, false);
il->set_item_tooltip_enabled(-1, false);
} }
} }
return sc; name_lists.append(il);
p_parent->add_child(il);
HSeparator *hs = memnew(HSeparator);
hs->set_modulate(Color(0, 0, 0, 0));
p_parent->add_child(hs);
return lbl;
} }
EditorAbout::EditorAbout() { EditorAbout::EditorAbout() {
set_title(TTR("Thanks from the Godot community!")); set_title(TTRC("Thanks from the Godot community!"));
set_hide_on_ok(true); set_hide_on_ok(true);
VBoxContainer *vbc = memnew(VBoxContainer); VBoxContainer *vbc = memnew(VBoxContainer);
@@ -224,13 +216,11 @@ EditorAbout::EditorAbout() {
version_info_vbc->add_child(memnew(EditorVersionButton(EditorVersionButton::FORMAT_WITH_NAME_AND_BUILD))); version_info_vbc->add_child(memnew(EditorVersionButton(EditorVersionButton::FORMAT_WITH_NAME_AND_BUILD)));
Label *about_text = memnew(Label); _about_text_label = memnew(Label);
about_text->set_focus_mode(Control::FOCUS_ACCESSIBILITY); _about_text_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
about_text->set_v_size_flags(Control::SIZE_SHRINK_CENTER); _about_text_label->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
about_text->set_text( _about_text_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
String::utf8("\xc2\xa9 2014-present ") + TTR("Godot Engine contributors") + "." + version_info_vbc->add_child(_about_text_label);
String::utf8("\n\xc2\xa9 2007-2014 Juan Linietsky, Ariel Manzur.\n"));
version_info_vbc->add_child(about_text);
hbc->add_child(version_info_vbc); hbc->add_child(version_info_vbc);
@@ -241,51 +231,50 @@ EditorAbout::EditorAbout() {
tc->set_theme_type_variation("TabContainerOdd"); tc->set_theme_type_variation("TabContainerOdd");
vbc->add_child(tc); vbc->add_child(tc);
// Authors. {
ScrollContainer *sc = memnew(ScrollContainer);
sc->set_name(TTRC("Authors"));
sc->set_v_size_flags(Control::SIZE_EXPAND);
tc->add_child(sc);
List<String> dev_sections; VBoxContainer *vb = memnew(VBoxContainer);
dev_sections.push_back(TTR("Project Founders")); vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
dev_sections.push_back(TTR("Lead Developer")); sc->add_child(vb);
// TRANSLATORS: This refers to a job title.
const String project_manager = TTR("Project Manager", "Job Title");
dev_sections.push_back(project_manager);
dev_sections.push_back(TTR("Developers"));
const char *const *dev_src[] = {
AUTHORS_FOUNDERS,
AUTHORS_LEAD_DEVELOPERS,
AUTHORS_PROJECT_MANAGERS,
AUTHORS_DEVELOPERS,
};
tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 0b1, false, project_manager)); // First section (Project Founders) is always one column.
// Donors. _create_section(vb, TTRC("Project Founders"), AUTHORS_FOUNDERS, FLAG_SINGLE_COLUMN);
_create_section(vb, TTRC("Lead Developer"), AUTHORS_LEAD_DEVELOPERS);
// The section title will be updated in NOTIFICATION_TRANSLATION_CHANGED.
_project_manager_label = _create_section(vb, "", AUTHORS_PROJECT_MANAGERS, FLAG_EASTER_EGG);
_project_manager_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
_create_section(vb, TTRC("Developers"), AUTHORS_DEVELOPERS);
}
List<String> donor_sections; {
donor_sections.push_back(TTR("Patrons")); ScrollContainer *sc = memnew(ScrollContainer);
donor_sections.push_back(TTR("Platinum Sponsors")); sc->set_name(TTRC("Donors"));
donor_sections.push_back(TTR("Gold Sponsors")); sc->set_v_size_flags(Control::SIZE_EXPAND);
donor_sections.push_back(TTR("Silver Sponsors")); tc->add_child(sc);
donor_sections.push_back(TTR("Diamond Members"));
donor_sections.push_back(TTR("Titanium Members")); VBoxContainer *vb = memnew(VBoxContainer);
donor_sections.push_back(TTR("Platinum Members")); vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
donor_sections.push_back(TTR("Gold Members")); sc->add_child(vb);
const char *const *donor_src[] = {
DONORS_PATRONS, _create_section(vb, TTRC("Patrons"), DONORS_PATRONS, FLAG_ALLOW_WEBSITE | FLAG_SINGLE_COLUMN);
DONORS_SPONSORS_PLATINUM, _create_section(vb, TTRC("Platinum Sponsors"), DONORS_SPONSORS_PLATINUM, FLAG_ALLOW_WEBSITE);
DONORS_SPONSORS_GOLD, _create_section(vb, TTRC("Gold Sponsors"), DONORS_SPONSORS_GOLD, FLAG_ALLOW_WEBSITE);
DONORS_SPONSORS_SILVER, _create_section(vb, TTRC("Silver Sponsors"), DONORS_SPONSORS_SILVER, FLAG_ALLOW_WEBSITE);
DONORS_MEMBERS_DIAMOND, _create_section(vb, TTRC("Diamond Members"), DONORS_MEMBERS_DIAMOND, FLAG_ALLOW_WEBSITE);
DONORS_MEMBERS_TITANIUM, _create_section(vb, TTRC("Titanium Members"), DONORS_MEMBERS_TITANIUM, FLAG_ALLOW_WEBSITE);
DONORS_MEMBERS_PLATINUM, _create_section(vb, TTRC("Platinum Members"), DONORS_MEMBERS_PLATINUM, FLAG_ALLOW_WEBSITE);
DONORS_MEMBERS_GOLD, _create_section(vb, TTRC("Gold Members"), DONORS_MEMBERS_GOLD, FLAG_ALLOW_WEBSITE);
}; }
tc->add_child(_populate_list(TTR("Donors"), donor_sections, donor_src, 0b1, true)); // First section (Patron) is one column.
// License. // License.
license_text_label = memnew(RichTextLabel); license_text_label = memnew(RichTextLabel);
license_text_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
license_text_label->set_threaded(true); license_text_label->set_threaded(true);
license_text_label->set_name(TTR("License")); license_text_label->set_name(TTRC("License"));
license_text_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); license_text_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
license_text_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); license_text_label->set_v_size_flags(Control::SIZE_EXPAND_FILL);
license_text_label->set_text(String::utf8(GODOT_LICENSE_TEXT)); license_text_label->set_text(String::utf8(GODOT_LICENSE_TEXT));
@@ -294,35 +283,37 @@ EditorAbout::EditorAbout() {
// Thirdparty License. // Thirdparty License.
VBoxContainer *license_thirdparty = memnew(VBoxContainer); VBoxContainer *license_thirdparty = memnew(VBoxContainer);
license_thirdparty->set_name(TTR("Third-party Licenses")); license_thirdparty->set_name(TTRC("Third-party Licenses"));
license_thirdparty->set_h_size_flags(Control::SIZE_EXPAND_FILL); license_thirdparty->set_h_size_flags(Control::SIZE_EXPAND_FILL);
tc->add_child(license_thirdparty); tc->add_child(license_thirdparty);
Label *tpl_label = memnew(Label); Label *tpl_label = memnew(Label(TTRC("Godot Engine relies on a number of third-party free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such third-party components with their respective copyright statements and license terms.")));
tpl_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY); tpl_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
tpl_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); tpl_label->set_h_size_flags(Control::SIZE_EXPAND_FILL);
tpl_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); tpl_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
tpl_label->set_text(TTR("Godot Engine relies on a number of third-party free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such third-party components with their respective copyright statements and license terms."));
tpl_label->set_size(Size2(630, 1) * EDSCALE); tpl_label->set_size(Size2(630, 1) * EDSCALE);
license_thirdparty->add_child(tpl_label); license_thirdparty->add_child(tpl_label);
HSplitContainer *tpl_hbc = memnew(HSplitContainer); HSplitContainer *tpl_hbc = memnew(HSplitContainer);
tpl_hbc->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
tpl_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); tpl_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
tpl_hbc->set_v_size_flags(Control::SIZE_EXPAND_FILL); tpl_hbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
tpl_hbc->set_split_offset(240 * EDSCALE); tpl_hbc->set_split_offset(240 * EDSCALE);
license_thirdparty->add_child(tpl_hbc); license_thirdparty->add_child(tpl_hbc);
_tpl_tree = memnew(Tree); _tpl_tree = memnew(Tree);
_tpl_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
_tpl_tree->set_hide_root(true); _tpl_tree->set_hide_root(true);
TreeItem *root = _tpl_tree->create_item(); TreeItem *root = _tpl_tree->create_item();
TreeItem *tpl_ti_all = _tpl_tree->create_item(root); TreeItem *tpl_ti_all = _tpl_tree->create_item(root);
tpl_ti_all->set_text(0, TTR("All Components")); tpl_ti_all->set_text(0, TTRC("All Components"));
tpl_ti_all->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_ALWAYS);
TreeItem *tpl_ti_tp = _tpl_tree->create_item(root); TreeItem *tpl_ti_tp = _tpl_tree->create_item(root);
tpl_ti_tp->set_text(0, TTR("Components")); tpl_ti_tp->set_text(0, TTRC("Components"));
tpl_ti_tp->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_ALWAYS);
tpl_ti_tp->set_selectable(0, false); tpl_ti_tp->set_selectable(0, false);
TreeItem *tpl_ti_lc = _tpl_tree->create_item(root); TreeItem *tpl_ti_lc = _tpl_tree->create_item(root);
tpl_ti_lc->set_text(0, TTR("Licenses")); tpl_ti_lc->set_text(0, TTRC("Licenses"));
tpl_ti_lc->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_ALWAYS);
tpl_ti_lc->set_selectable(0, false); tpl_ti_lc->set_selectable(0, false);
String long_text = ""; String long_text = "";
for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) { for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) {

View File

@@ -31,13 +31,13 @@
#pragma once #pragma once
#include "scene/gui/dialogs.h" #include "scene/gui/dialogs.h"
#include "scene/gui/item_list.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/scroll_container.h"
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
class CreditsRoll; class CreditsRoll;
class ItemList;
class Label;
class RichTextLabel;
class TextureRect;
class Tree;
/** /**
* NOTE: Do not assume the EditorNode singleton to be available in this class' methods. * NOTE: Do not assume the EditorNode singleton to be available in this class' methods.
@@ -47,12 +47,19 @@ class EditorAbout : public AcceptDialog {
GDCLASS(EditorAbout, AcceptDialog); GDCLASS(EditorAbout, AcceptDialog);
private: private:
void _license_tree_selected(); enum SectionFlags {
void _project_manager_clicked(); FLAG_SINGLE_COLUMN = 1 << 0,
void _item_with_website_selected(int p_id, ItemList *p_il); FLAG_ALLOW_WEBSITE = 1 << 1,
void _item_list_resized(ItemList *p_il); FLAG_EASTER_EGG = 1 << 2,
ScrollContainer *_populate_list(const String &p_name, const List<String> &p_sections, const char *const *const p_src[], int p_single_column_flags = 0, bool p_allow_website = false, const String &p_easter_egg_section = String()); };
void _license_tree_selected();
void _item_activated(int p_idx, ItemList *p_il);
void _item_list_resized(ItemList *p_il);
Label *_create_section(Control *p_parent, const String &p_name, const char *const *p_src, BitField<SectionFlags> p_flags = 0);
Label *_about_text_label = nullptr;
Label *_project_manager_label = nullptr;
Tree *_tpl_tree = nullptr; Tree *_tpl_tree = nullptr;
RichTextLabel *license_text_label = nullptr; RichTextLabel *license_text_label = nullptr;
RichTextLabel *_tpl_text = nullptr; RichTextLabel *_tpl_text = nullptr;