diff --git a/core/object/object.cpp b/core/object/object.cpp index 39f20a212f81..da8a30e61e6c 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -485,7 +485,7 @@ void Object::get_property_list(List *p_list, bool p_reversed) cons if (_extension) { const ObjectGDExtension *current_extension = _extension; while (current_extension) { - p_list->push_back(PropertyInfo(Variant::NIL, current_extension->class_name, PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); + p_list->push_back(PropertyInfo(Variant::NIL, current_extension->class_name, PROPERTY_HINT_NONE, current_extension->class_name, PROPERTY_USAGE_CATEGORY)); ClassDB::get_property_list(current_extension->class_name, p_list, true, this); diff --git a/core/object/object.h b/core/object/object.h index 675b6cc1d815..309cd34c4bbb 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -489,7 +489,7 @@ protected: if (!p_reversed) { \ m_inherits::_get_property_listv(p_list, p_reversed); \ } \ - p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); \ + p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, get_class_static(), PROPERTY_USAGE_CATEGORY)); \ if (!_is_gpl_reversed()) { \ ::ClassDB::get_property_list(#m_class, p_list, true, this); \ } \ diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index bcb7c003c220..b3ca04420fa8 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -811,26 +811,30 @@ ConnectDialog::~ConnectDialog() { // Originally copied and adapted from EditorProperty, try to keep style in sync. Control *ConnectionsDockTree::make_custom_tooltip(const String &p_text) const { - EditorHelpBit *help_bit = memnew(EditorHelpBit); - help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); - - // p_text is expected to be something like this: - // "gui_input::(event: InputEvent)::" - // with the latter being possibly empty. - PackedStringArray slices = p_text.split("::", false); - if (slices.size() < 2) { - // Shouldn't happen here, but just in case pass the text along. - help_bit->set_text(p_text); - return help_bit; - } - - String text = TTR("Signal:") + " [u][b]" + slices[0] + "[/b][/u]"; - text += slices[1].strip_edges() + "\n"; - if (slices.size() > 2) { - text += slices[2].strip_edges(); - } else { + // `p_text` is expected to be something like this: + // - `class|Control||Control brief description.`; + // - `signal|gui_input|(event: InputEvent)|gui_input description.`; + // - `../../.. :: _on_gui_input()`. + // Note that the description can be empty or contain `|`. + PackedStringArray slices = p_text.split("|", true, 3); + if (slices.size() < 4) { + return nullptr; // Use default tooltip instead. + } + + String item_type = (slices[0] == "class") ? TTR("Class:") : TTR("Signal:"); + String item_name = slices[1].strip_edges(); + String item_params = slices[2].strip_edges(); + String item_descr = slices[3].strip_edges(); + + String text = item_type + " [u][b]" + item_name + "[/b][/u]" + item_params + "\n"; + if (item_descr.is_empty()) { text += "[i]" + TTR("No description.") + "[/i]"; + } else { + text += item_descr; } + + EditorHelpBit *help_bit = memnew(EditorHelpBit); + help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); help_bit->set_text(text); return help_bit; @@ -960,8 +964,7 @@ void ConnectionsDock::_disconnect(const ConnectDialog::ConnectionData &p_cd) { */ void ConnectionsDock::_disconnect_all() { TreeItem *item = tree->get_selected(); - - if (!_is_item_signal(*item)) { + if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) { return; } @@ -990,38 +993,44 @@ void ConnectionsDock::_disconnect_all() { void ConnectionsDock::_tree_item_selected() { TreeItem *item = tree->get_selected(); - if (!item) { // Unlikely. Disable button just in case. - connect_button->set_text(TTR("Connect...")); - connect_button->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); - connect_button->set_disabled(true); - } else if (_is_item_signal(*item)) { + if (item && _get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) { connect_button->set_text(TTR("Connect...")); connect_button->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); connect_button->set_disabled(false); - } else { + } else if (item && _get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) { connect_button->set_text(TTR("Disconnect")); connect_button->set_icon(get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons"))); connect_button->set_disabled(false); + } else { + connect_button->set_text(TTR("Connect...")); + connect_button->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); + connect_button->set_disabled(true); } } void ConnectionsDock::_tree_item_activated() { // "Activation" on double-click. - TreeItem *item = tree->get_selected(); - if (!item) { return; } - if (_is_item_signal(*item)) { + if (_get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) { _open_connection_dialog(*item); - } else { - _go_to_script(*item); + } else if (_get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) { + _go_to_method(*item); } } -bool ConnectionsDock::_is_item_signal(TreeItem &p_item) { - return (p_item.get_parent() == tree->get_root() || p_item.get_parent()->get_parent() == tree->get_root()); +ConnectionsDock::TreeItemType ConnectionsDock::_get_item_type(const TreeItem &p_item) const { + if (&p_item == tree->get_root()) { + return TREE_ITEM_TYPE_ROOT; + } else if (p_item.get_parent() == tree->get_root()) { + return TREE_ITEM_TYPE_CLASS; + } else if (p_item.get_parent()->get_parent() == tree->get_root()) { + return TREE_ITEM_TYPE_SIGNAL; + } else { + return TREE_ITEM_TYPE_CONNECTION; + } } bool ConnectionsDock::_is_connection_inherited(Connection &p_connection) { @@ -1077,8 +1086,8 @@ void ConnectionsDock::_open_edit_connection_dialog(TreeItem &p_item) { /* * Open slot method location in script editor. */ -void ConnectionsDock::_go_to_script(TreeItem &p_item) { - if (_is_item_signal(p_item)) { +void ConnectionsDock::_go_to_method(TreeItem &p_item) { + if (_get_item_type(p_item) != TREE_ITEM_TYPE_CONNECTION) { return; } @@ -1101,27 +1110,39 @@ void ConnectionsDock::_go_to_script(TreeItem &p_item) { } } +void ConnectionsDock::_handle_class_menu_option(int p_option) { + switch (p_option) { + case CLASS_MENU_OPEN_DOCS: + ScriptEditor::get_singleton()->goto_help("class:" + class_menu_doc_class_name); + EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); + break; + } +} + +void ConnectionsDock::_class_menu_about_to_popup() { + class_menu->set_item_disabled(class_menu->get_item_index(CLASS_MENU_OPEN_DOCS), class_menu_doc_class_name.is_empty()); +} + void ConnectionsDock::_handle_signal_menu_option(int p_option) { TreeItem *item = tree->get_selected(); - - if (!item) { + if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) { return; } Dictionary meta = item->get_metadata(0); switch (p_option) { - case CONNECT: { + case SIGNAL_MENU_CONNECT: { _open_connection_dialog(*item); } break; - case DISCONNECT_ALL: { + case SIGNAL_MENU_DISCONNECT_ALL: { disconnect_all_dialog->set_text(vformat(TTR("Are you sure you want to remove all connections from the \"%s\" signal?"), meta["name"])); disconnect_all_dialog->popup_centered(); } break; - case COPY_NAME: { + case SIGNAL_MENU_COPY_NAME: { DisplayServer::get_singleton()->clipboard_set(meta["name"]); } break; - case OPEN_DOCUMENTATION: { + case SIGNAL_MENU_OPEN_DOCS: { ScriptEditor::get_singleton()->goto_help("class_signal:" + String(meta["class"]) + ":" + String(meta["name"])); EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); } break; @@ -1130,8 +1151,7 @@ void ConnectionsDock::_handle_signal_menu_option(int p_option) { void ConnectionsDock::_signal_menu_about_to_popup() { TreeItem *item = tree->get_selected(); - - if (!item) { + if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_SIGNAL) { return; } @@ -1144,25 +1164,24 @@ void ConnectionsDock::_signal_menu_about_to_popup() { } } - signal_menu->set_item_disabled(signal_menu->get_item_index(DISCONNECT_ALL), disable_disconnect_all); - signal_menu->set_item_disabled(signal_menu->get_item_index(OPEN_DOCUMENTATION), String(meta["class"]).is_empty()); + signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_DISCONNECT_ALL), disable_disconnect_all); + signal_menu->set_item_disabled(signal_menu->get_item_index(SIGNAL_MENU_OPEN_DOCS), String(meta["class"]).is_empty()); } void ConnectionsDock::_handle_slot_menu_option(int p_option) { TreeItem *item = tree->get_selected(); - - if (!item) { + if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_CONNECTION) { return; } switch (p_option) { - case EDIT: { + case SLOT_MENU_EDIT: { _open_edit_connection_dialog(*item); } break; - case GO_TO_SCRIPT: { - _go_to_script(*item); + case SLOT_MENU_GO_TO_METHOD: { + _go_to_method(*item); } break; - case DISCONNECT: { + case SLOT_MENU_DISCONNECT: { Connection connection = item->get_metadata(0); _disconnect(connection); update_tree(); @@ -1171,33 +1190,50 @@ void ConnectionsDock::_handle_slot_menu_option(int p_option) { } void ConnectionsDock::_slot_menu_about_to_popup() { - bool connection_is_inherited = tree->get_selected()->has_meta("_inherited_connection"); + TreeItem *item = tree->get_selected(); + if (!item || _get_item_type(*item) != TREE_ITEM_TYPE_CONNECTION) { + return; + } + + bool connection_is_inherited = item->has_meta("_inherited_connection"); - slot_menu->set_item_disabled(slot_menu->get_item_index(EDIT), connection_is_inherited); - slot_menu->set_item_disabled(slot_menu->get_item_index(DISCONNECT), connection_is_inherited); + slot_menu->set_item_disabled(slot_menu->get_item_index(SLOT_MENU_EDIT), connection_is_inherited); + slot_menu->set_item_disabled(slot_menu->get_item_index(SLOT_MENU_DISCONNECT), connection_is_inherited); } -void ConnectionsDock::_rmb_pressed(Vector2 p_position, MouseButton p_button) { - if (p_button != MouseButton::RIGHT) { +void ConnectionsDock::_rmb_pressed(const Ref &p_event) { + const Ref &mb_event = p_event; + if (mb_event.is_null() || !mb_event->is_pressed() || mb_event->get_button_index() != MouseButton::RIGHT) { return; } - TreeItem *item = tree->get_selected(); - + TreeItem *item = tree->get_item_at_position(mb_event->get_position()); if (!item) { return; } - Vector2 screen_position = tree->get_screen_position() + p_position; + Vector2 screen_position = tree->get_screen_position() + mb_event->get_position(); - if (_is_item_signal(*item)) { - signal_menu->set_position(screen_position); - signal_menu->reset_size(); - signal_menu->popup(); - } else { - slot_menu->set_position(screen_position); - slot_menu->reset_size(); - slot_menu->popup(); + switch (_get_item_type(*item)) { + case TREE_ITEM_TYPE_ROOT: + break; + case TREE_ITEM_TYPE_CLASS: + class_menu_doc_class_name = item->get_metadata(0); + class_menu->set_position(screen_position); + class_menu->reset_size(); + class_menu->popup(); + accept_event(); // Don't collapse item. + break; + case TREE_ITEM_TYPE_SIGNAL: + signal_menu->set_position(screen_position); + signal_menu->reset_size(); + signal_menu->popup(); + break; + case TREE_ITEM_TYPE_CONNECTION: + slot_menu->set_position(screen_position); + slot_menu->reset_size(); + slot_menu->popup(); + break; } } @@ -1212,9 +1248,9 @@ void ConnectionsDock::_connect_pressed() { return; } - if (_is_item_signal(*item)) { + if (_get_item_type(*item) == TREE_ITEM_TYPE_SIGNAL) { _open_connection_dialog(*item); - } else { + } else if (_get_item_type(*item) == TREE_ITEM_TYPE_CONNECTION) { Connection connection = item->get_metadata(0); _disconnect(connection); update_tree(); @@ -1227,14 +1263,16 @@ void ConnectionsDock::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { search_box->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - signal_menu->set_item_icon(signal_menu->get_item_index(CONNECT), get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); - signal_menu->set_item_icon(signal_menu->get_item_index(DISCONNECT_ALL), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons"))); - signal_menu->set_item_icon(signal_menu->get_item_index(COPY_NAME), get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons"))); - signal_menu->set_item_icon(signal_menu->get_item_index(OPEN_DOCUMENTATION), get_theme_icon(SNAME("Help"), SNAME("EditorIcons"))); + class_menu->set_item_icon(class_menu->get_item_index(CLASS_MENU_OPEN_DOCS), get_theme_icon(SNAME("Help"), SNAME("EditorIcons"))); + + signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_CONNECT), get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); + signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_DISCONNECT_ALL), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons"))); + signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_COPY_NAME), get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons"))); + signal_menu->set_item_icon(signal_menu->get_item_index(SIGNAL_MENU_OPEN_DOCS), get_theme_icon(SNAME("Help"), SNAME("EditorIcons"))); - slot_menu->set_item_icon(slot_menu->get_item_index(EDIT), get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - slot_menu->set_item_icon(slot_menu->get_item_index(GO_TO_SCRIPT), get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons"))); - slot_menu->set_item_icon(slot_menu->get_item_index(DISCONNECT), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons"))); + slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_EDIT), get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_GO_TO_METHOD), get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons"))); + slot_menu->set_item_icon(slot_menu->get_item_index(SLOT_MENU_DISCONNECT), get_theme_icon(SNAME("Unlinked"), SNAME("EditorIcons"))); } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { @@ -1272,6 +1310,7 @@ void ConnectionsDock::update_tree() { while (native_base != StringName()) { String class_name; String doc_class_name; + String class_brief; Ref class_icon; List class_signals; @@ -1293,6 +1332,7 @@ void ConnectionsDock::update_tree() { } HashMap::ConstIterator F = doc_data->class_list.find(doc_class_name); if (F) { + class_brief = F->value.brief_description; for (int i = 0; i < F->value.signals.size(); i++) { descr_cache[doc_class_name][F->value.signals[i].name] = F->value.signals[i].description; } @@ -1329,16 +1369,17 @@ void ConnectionsDock::update_tree() { class_name = native_base; doc_class_name = class_name; - // For a native class, the cache is filled once. - if (!descr_cache.has(doc_class_name)) { - HashMap::ConstIterator F = doc_data->class_list.find(doc_class_name); - if (F) { + HashMap::ConstIterator F = doc_data->class_list.find(doc_class_name); + if (F) { + class_brief = DTR(F->value.brief_description); + // For a native class, the cache is filled once. + if (!descr_cache.has(doc_class_name)) { for (int i = 0; i < F->value.signals.size(); i++) { descr_cache[doc_class_name][F->value.signals[i].name] = DTR(F->value.signals[i].description); } - } else { - doc_class_name = String(); } + } else { + doc_class_name = String(); } if (has_theme_icon(native_base, SNAME("EditorIcons"))) { @@ -1362,14 +1403,17 @@ void ConnectionsDock::update_tree() { section_item = tree->create_item(root); section_item->set_text(0, class_name); + // `|` separators used in `make_custom_tooltip()` for formatting. + section_item->set_tooltip_text(0, "class|" + class_name + "||" + class_brief); section_item->set_icon(0, class_icon); section_item->set_selectable(0, false); section_item->set_editable(0, false); section_item->set_custom_bg_color(0, get_theme_color(SNAME("prop_subsection"), SNAME("Editor"))); + section_item->set_metadata(0, doc_class_name); } for (MethodInfo &mi : class_signals) { - const StringName signal_name = mi.name; + const StringName &signal_name = mi.name; if (!search_box->get_text().is_subsequence_ofn(signal_name)) { continue; } @@ -1404,8 +1448,8 @@ void ConnectionsDock::update_tree() { } } - // "::" separators used in make_custom_tooltip for formatting. - signal_item->set_tooltip_text(0, String(signal_name) + "::" + signame.trim_prefix(mi.name) + "::" + descr); + // `|` separators used in `make_custom_tooltip()` for formatting. + signal_item->set_tooltip_text(0, "signal|" + String(signal_name) + "|" + signame.trim_prefix(mi.name) + "|" + descr); } // List existing connections. @@ -1500,28 +1544,34 @@ ConnectionsDock::ConnectionsDock() { disconnect_all_dialog->connect("confirmed", callable_mp(this, &ConnectionsDock::_disconnect_all)); disconnect_all_dialog->set_text(TTR("Are you sure you want to remove all connections from this signal?")); + class_menu = memnew(PopupMenu); + class_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_class_menu_option)); + class_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_class_menu_about_to_popup)); + class_menu->add_item(TTR("Open Documentation"), CLASS_MENU_OPEN_DOCS); + add_child(class_menu); + signal_menu = memnew(PopupMenu); - add_child(signal_menu); signal_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_signal_menu_option)); signal_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_signal_menu_about_to_popup)); - signal_menu->add_item(TTR("Connect..."), CONNECT); - signal_menu->add_item(TTR("Disconnect All"), DISCONNECT_ALL); - signal_menu->add_item(TTR("Copy Name"), COPY_NAME); + signal_menu->add_item(TTR("Connect..."), SIGNAL_MENU_CONNECT); + signal_menu->add_item(TTR("Disconnect All"), SIGNAL_MENU_DISCONNECT_ALL); + signal_menu->add_item(TTR("Copy Name"), SIGNAL_MENU_COPY_NAME); signal_menu->add_separator(); - signal_menu->add_item(TTR("Open Documentation"), OPEN_DOCUMENTATION); + signal_menu->add_item(TTR("Open Documentation"), SIGNAL_MENU_OPEN_DOCS); + add_child(signal_menu); slot_menu = memnew(PopupMenu); - add_child(slot_menu); slot_menu->connect("id_pressed", callable_mp(this, &ConnectionsDock::_handle_slot_menu_option)); slot_menu->connect("about_to_popup", callable_mp(this, &ConnectionsDock::_slot_menu_about_to_popup)); - slot_menu->add_item(TTR("Edit..."), EDIT); - slot_menu->add_item(TTR("Go to Method"), GO_TO_SCRIPT); - slot_menu->add_item(TTR("Disconnect"), DISCONNECT); + slot_menu->add_item(TTR("Edit..."), SLOT_MENU_EDIT); + slot_menu->add_item(TTR("Go to Method"), SLOT_MENU_GO_TO_METHOD); + slot_menu->add_item(TTR("Disconnect"), SLOT_MENU_DISCONNECT); + add_child(slot_menu); connect_dialog->connect("connected", callable_mp(this, &ConnectionsDock::_make_or_edit_connection)); tree->connect("item_selected", callable_mp(this, &ConnectionsDock::_tree_item_selected)); tree->connect("item_activated", callable_mp(this, &ConnectionsDock::_tree_item_activated)); - tree->connect("item_mouse_selected", callable_mp(this, &ConnectionsDock::_rmb_pressed)); + tree->connect("gui_input", callable_mp(this, &ConnectionsDock::_rmb_pressed)); add_theme_constant_override("separation", 3 * EDSCALE); } diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index d728072c2e4a..b07b08ecc7c6 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -196,18 +196,27 @@ class ConnectionsDockTree : public Tree { class ConnectionsDock : public VBoxContainer { GDCLASS(ConnectionsDock, VBoxContainer); - // Right-click popup menu options. - enum SignalMenuOption { - CONNECT, - DISCONNECT_ALL, - COPY_NAME, - OPEN_DOCUMENTATION, + enum TreeItemType { + TREE_ITEM_TYPE_ROOT, + TREE_ITEM_TYPE_CLASS, + TREE_ITEM_TYPE_SIGNAL, + TREE_ITEM_TYPE_CONNECTION, }; + // Right-click context menu options. + enum ClassMenuOption { + CLASS_MENU_OPEN_DOCS, + }; + enum SignalMenuOption { + SIGNAL_MENU_CONNECT, + SIGNAL_MENU_DISCONNECT_ALL, + SIGNAL_MENU_COPY_NAME, + SIGNAL_MENU_OPEN_DOCS, + }; enum SlotMenuOption { - EDIT, - GO_TO_SCRIPT, - DISCONNECT, + SLOT_MENU_EDIT, + SLOT_MENU_GO_TO_METHOD, + SLOT_MENU_DISCONNECT, }; Node *selected_node = nullptr; @@ -216,6 +225,8 @@ class ConnectionsDock : public VBoxContainer { ConfirmationDialog *disconnect_all_dialog = nullptr; ConnectDialog *connect_dialog = nullptr; Button *connect_button = nullptr; + PopupMenu *class_menu = nullptr; + String class_menu_doc_class_name; PopupMenu *signal_menu = nullptr; PopupMenu *slot_menu = nullptr; LineEdit *search_box = nullptr; @@ -231,18 +242,20 @@ class ConnectionsDock : public VBoxContainer { void _tree_item_selected(); void _tree_item_activated(); - bool _is_item_signal(TreeItem &p_item); + TreeItemType _get_item_type(const TreeItem &p_item) const; bool _is_connection_inherited(Connection &p_connection); void _open_connection_dialog(TreeItem &p_item); void _open_edit_connection_dialog(TreeItem &p_item); - void _go_to_script(TreeItem &p_item); + void _go_to_method(TreeItem &p_item); + void _handle_class_menu_option(int p_option); + void _class_menu_about_to_popup(); void _handle_signal_menu_option(int p_option); void _signal_menu_about_to_popup(); void _handle_slot_menu_option(int p_option); void _slot_menu_about_to_popup(); - void _rmb_pressed(Vector2 p_position, MouseButton p_button); + void _rmb_pressed(const Ref &p_event); void _close(); protected: diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index db61700c0013..5b74108e2088 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -904,36 +904,34 @@ void EditorProperty::_update_pin_flags() { } } -static Control *make_help_bit(const String &p_text, const String &p_warning, const Color &p_warn_color, bool p_property) { - EditorHelpBit *help_bit = memnew(EditorHelpBit); - help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); - - PackedStringArray slices = p_text.split("::", false); - if (slices.is_empty()) { - // Shouldn't happen here, but just in case pass the text along. - help_bit->set_text(p_text); - return help_bit; +static Control *make_help_bit(const String &p_item_type, const String &p_text, const String &p_warning, const Color &p_warn_color) { + // `p_text` is expected to be something like this: + // `item_name|Item description.`. + // Note that the description can be empty or contain `|`. + PackedStringArray slices = p_text.split("|", true, 1); + if (slices.size() < 2) { + return nullptr; // Use default tooltip instead. } - String property_name = slices[0].strip_edges(); + String item_name = slices[0].strip_edges(); + String item_descr = slices[1].strip_edges(); + String text; - if (p_property) { - text = TTR("Property:") + " "; + if (!p_item_type.is_empty()) { + text = p_item_type + " "; } - text += "[u][b]" + property_name + "[/b][/u]"; - - if (slices.size() > 1) { - String property_doc = slices[1].strip_edges(); - if (property_name != property_doc) { - text += "\n" + property_doc; - } + text += "[u][b]" + item_name + "[/b][/u]\n"; + if (item_descr.is_empty()) { + text += "[i]" + TTR("No description.") + "[/i]"; } else { - text += "\n[i]" + TTR("No description.") + "[/i]"; + text += item_descr; } - if (!p_warning.is_empty()) { text += "\n[b][color=" + p_warn_color.to_html(false) + "]" + p_warning + "[/color][/b]"; } + + EditorHelpBit *help_bit = memnew(EditorHelpBit); + help_bit->get_rich_text()->set_custom_minimum_size(Size2(360 * EDSCALE, 1)); help_bit->set_text(text); return help_bit; @@ -946,7 +944,7 @@ Control *EditorProperty::make_custom_tooltip(const String &p_text) const { warn = object->call("_get_property_warning", property); warn_color = get_theme_color(SNAME("warning_color")); } - return make_help_bit(p_text, warn, warn_color, true); + return make_help_bit(TTR("Property:"), p_text, warn, warn_color); } void EditorProperty::menu_option(int p_option) { @@ -1142,6 +1140,10 @@ void EditorInspectorPlugin::_bind_methods() { void EditorInspectorCategory::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + menu->set_item_icon(menu->get_item_index(MENU_OPEN_DOCS), get_theme_icon(SNAME("Help"), SNAME("EditorIcons"))); + } break; case NOTIFICATION_DRAW: { Ref sb = get_theme_stylebox(SNAME("bg")); @@ -1175,7 +1177,7 @@ void EditorInspectorCategory::_notification(int p_what) { } Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) const { - return make_help_bit(p_text, String(), Color(), false); + return make_help_bit(TTR("Class:"), p_text, String(), Color()); } Size2 EditorInspectorCategory::get_minimum_size() const { @@ -1192,7 +1194,37 @@ Size2 EditorInspectorCategory::get_minimum_size() const { return ms; } +void EditorInspectorCategory::_handle_menu_option(int p_option) { + switch (p_option) { + case MENU_OPEN_DOCS: + ScriptEditor::get_singleton()->goto_help("class:" + doc_class_name); + EditorNode::get_singleton()->set_visible_editor(EditorNode::EDITOR_SCRIPT); + break; + } +} + +void EditorInspectorCategory::gui_input(const Ref &p_event) { + if (doc_class_name.is_empty()) { + return; + } + + const Ref &mb_event = p_event; + if (mb_event.is_null() || !mb_event->is_pressed() || mb_event->get_button_index() != MouseButton::RIGHT) { + return; + } + + menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name)); + + menu->set_position(get_screen_position() + mb_event->get_position()); + menu->reset_size(); + menu->popup(); +} + EditorInspectorCategory::EditorInspectorCategory() { + menu = memnew(PopupMenu); + menu->connect("id_pressed", callable_mp(this, &EditorInspectorCategory::_handle_menu_option)); + menu->add_item(TTR("Open Documentation"), MENU_OPEN_DOCS); + add_child(menu); } //////////////////////////////////////////////// @@ -2801,6 +2833,14 @@ void EditorInspector::update_tree() { main_vbox->add_child(category); category_vbox = nullptr; //reset + // `hint_script` should contain a native class name or a script path. + // Otherwise the category was probably added via `@export_category` or `_get_property_list()`. + if (p.hint_string.is_empty()) { + category->label = p.name; + category->set_tooltip_text(p.name); + continue; // Do not add an icon, do not change the current class (`doc_name`). + } + String type = p.name; String label = p.name; doc_name = p.name; @@ -2837,6 +2877,7 @@ void EditorInspector::update_tree() { // Set the category label. category->label = label; + category->doc_class_name = doc_name; if (use_doc_hints) { String descr = ""; @@ -2845,16 +2886,18 @@ void EditorInspector::update_tree() { DocTools *dd = EditorHelp::get_doc_data(); HashMap::Iterator E = dd->class_list.find(doc_name); if (E) { - descr = DTR(E->value.brief_description); + descr = E->value.brief_description; } if (ClassDB::class_exists(doc_name)) { + descr = DTR(descr); // Do not translate the class description of scripts. class_descr_cache[doc_name] = descr; // Do not cache the class description of scripts. } } else { descr = class_descr_cache[doc_name]; } - category->set_tooltip_text(p.name + "::" + descr); + // `|` separator used in `make_help_bit()` for formatting. + category->set_tooltip_text(p.name + "|" + descr); } // Add editors at the start of a category. @@ -3196,13 +3239,18 @@ void EditorInspector::update_tree() { } if (!found) { + bool is_native_class = ClassDB::class_exists(classname); + // Build the property description String and add it to the cache. DocTools *dd = EditorHelp::get_doc_data(); HashMap::ConstIterator F = dd->class_list.find(classname); while (F && doc_info.description.is_empty()) { for (int i = 0; i < F->value.properties.size(); i++) { if (F->value.properties[i].name == propname.operator String()) { - doc_info.description = DTR(F->value.properties[i].description); + doc_info.description = F->value.properties[i].description; + if (is_native_class) { + doc_info.description = DTR(doc_info.description); // Do not translate the property description of scripts. + } const Vector class_enum = F->value.properties[i].enumeration.split("."); const String class_name = class_enum[0]; @@ -3215,7 +3263,11 @@ void EditorInspector::update_tree() { if (val.enumeration == enum_name && !val.name.ends_with("_MAX")) { const String enum_value = EditorPropertyNameProcessor::get_singleton()->process_name(val.name, EditorPropertyNameProcessor::STYLE_CAPITALIZED); // Prettify the enum value display, so that "_" becomes "Value". - String desc = DTR(val.description).trim_prefix("\n"); + String desc = val.description; + if (is_native_class) { + desc = DTR(desc); // Do not translate the enum value description of scripts. + } + desc = desc.trim_prefix("\n"); doc_info.description += vformat( "\n[b]%s:[/b] %s", enum_value.trim_prefix(EditorPropertyNameProcessor::get_singleton()->process_name(enum_name, EditorPropertyNameProcessor::STYLE_CAPITALIZED) + " "), @@ -3234,7 +3286,10 @@ void EditorInspector::update_tree() { if (slices.size() == 2 && slices[0].begins_with("theme_override_")) { for (int i = 0; i < F->value.theme_properties.size(); i++) { if (F->value.theme_properties[i].name == slices[1]) { - doc_info.description = DTR(F->value.theme_properties[i].description); + doc_info.description = F->value.theme_properties[i].description; + if (is_native_class) { + doc_info.description = DTR(doc_info.description); // Do not translate the theme item description of scripts. + } doc_info.path = "class_theme_item:" + F->value.name + ":" + F->value.theme_properties[i].name; break; } @@ -3248,7 +3303,7 @@ void EditorInspector::update_tree() { } } - if (ClassDB::class_exists(classname)) { + if (is_native_class) { doc_info_cache[classname][propname] = doc_info; // Do not cache the doc information of scripts. } } @@ -3340,11 +3395,8 @@ void EditorInspector::update_tree() { ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed)); ep->connect("resource_selected", callable_mp(this, &EditorInspector::_resource_selected), CONNECT_DEFERRED); ep->connect("object_id_selected", callable_mp(this, &EditorInspector::_object_id_selected), CONNECT_DEFERRED); - if (!doc_info.description.is_empty()) { - ep->set_tooltip_text(property_prefix + p.name + "::" + doc_info.description); - } else { - ep->set_tooltip_text(property_prefix + p.name); - } + // `|` separator used in `make_help_bit()` for formatting. + ep->set_tooltip_text(property_prefix + p.name + "|" + doc_info.description); ep->set_doc_path(doc_info.path); ep->update_property(); ep->_update_pin_flags(); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 63d3db9b8954..4393922f52cb 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -253,11 +253,22 @@ class EditorInspectorCategory : public Control { GDCLASS(EditorInspectorCategory, Control); friend class EditorInspector; + + // Right-click context menu options. + enum ClassMenuOption { + MENU_OPEN_DOCS, + }; + Ref icon; String label; + String doc_class_name; + PopupMenu *menu = nullptr; + + void _handle_menu_option(int p_option); protected: void _notification(int p_what); + virtual void gui_input(const Ref &p_event) override; public: virtual Size2 get_minimum_size() const override;