diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 7f15eca11dc4..c9a31384d350 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1997,7 +1997,8 @@ void EditorHelp::_help_callback(const String &p_topic) { } } -static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control *p_owner_node, const String &p_class = "") { +// TODO: Should probably move this to a shared utility file/class so it can be accessed by both 'editor_help.cpp' and 'symbol_tooltip.cpp' +void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control *p_owner_node, const String &p_class) { DocTools *doc = EditorHelp::get_doc_data(); String base_path; diff --git a/editor/editor_help.h b/editor/editor_help.h index 49d13c552281..17a29cef9b26 100644 --- a/editor/editor_help.h +++ b/editor/editor_help.h @@ -283,6 +283,8 @@ class EditorHelpBit : public MarginContainer { EditorHelpBit(); }; +void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt, Control *p_owner_node, const String &p_class = ""); + class EditorHelpTooltip : public EditorHelpBit { GDCLASS(EditorHelpTooltip, EditorHelpBit); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 06aedecd0ee7..3ad92102afff 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1948,6 +1948,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data void ScriptTextEditor::_text_edit_gui_input(const Ref &ev) { Ref mb = ev; + Ref mm = ev; Ref k = ev; Point2 local_pos; bool create_menu = false; @@ -1956,6 +1957,10 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref &ev) { if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { local_pos = mb->get_global_position() - tx->get_global_position(); create_menu = true; + } else if (mm.is_valid()) { + if (script->is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { + symbol_tooltip->update_symbol_tooltip(mm->get_position(), script); + } } else if (k.is_valid() && k->is_action("ui_menu", true)) { tx->adjust_viewport_to_caret(0); local_pos = tx->get_caret_draw_pos(0); @@ -2416,6 +2421,9 @@ ScriptTextEditor::ScriptTextEditor() { connection_info_dialog = memnew(ConnectionInfoDialog); SET_DRAG_FORWARDING_GCD(code_editor->get_text_editor(), ScriptTextEditor); + + symbol_tooltip = memnew(SymbolTooltip(code_editor->get_text_editor())); + add_child(symbol_tooltip); } ScriptTextEditor::~ScriptTextEditor() { diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 8c0ba6d7e669..87f4e2cccef7 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -34,6 +34,7 @@ #include "script_editor_plugin.h" #include "editor/code_editor.h" +#include "editor/symbol_tooltip.h" #include "scene/gui/color_picker.h" #include "scene/gui/dialogs.h" #include "scene/gui/tree.h" @@ -159,6 +160,8 @@ class ScriptTextEditor : public ScriptEditorBase { void _enable_code_editor(); + SymbolTooltip *symbol_tooltip = nullptr; + protected: void _update_breakpoint_list(); void _breakpoint_item_pressed(int p_idx); diff --git a/editor/symbol_tooltip.cpp b/editor/symbol_tooltip.cpp new file mode 100644 index 000000000000..2aa6735cce33 --- /dev/null +++ b/editor/symbol_tooltip.cpp @@ -0,0 +1,659 @@ +/**************************************************************************/ +/* symbol_tooltip.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "symbol_tooltip.h" +#include "core/config/project_settings.h" +#include "editor/plugins/script_text_editor.h" +#include "editor/editor_string_names.h" +#include "editor_help.h" +#include "modules/gdscript/editor/gdscript_highlighter.h" +#include + +SymbolTooltip::SymbolTooltip(CodeEdit *p_text_editor) : + text_editor(p_text_editor) { + Ref highlighter; + highlighter.instantiate(); + + // Set the tooltip's theme (PanelContainer's theme) + //set_theme(EditorNode::get_singleton()->get_gui_base()->get_theme()); + + set_as_top_level(true); // Prevents the tooltip from affecting the editor's layout. + hide(); + /*set_v_size_flags(0); + set_h_size_flags(0);*/ + set_z_index(1000); + set_theme(_create_panel_theme()); + set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + //set_size(Size2(400, 400)); + //set_position(Size2(800, 800)); + set_process(true); + //set_clip_contents(true); + + // Create VBoxContainer to hold the tooltip's header and body. + layout_container = memnew(VBoxContainer); + layout_container->add_theme_constant_override("separation", 0); + layout_container->set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + add_child(layout_container); + + // Create RichTextLabel for the tooltip's header. + header_label = memnew(TextEdit); + header_label->set_focus_mode(Control::FOCUS_ALL); + header_label->set_context_menu_enabled(false); + header_label->set_h_scroll_visibility(false); + header_label->set_v_scroll_visibility(false); + header_label->set_syntax_highlighter(highlighter); + header_label->set_custom_minimum_size(Size2(50, 45)); + + header_label->set_v_size_flags(Control::SIZE_SHRINK_BEGIN); + header_label->set_line_wrapping_mode(TextEdit::LINE_WRAPPING_BOUNDARY); + header_label->set_fit_content_height_enabled(true); + + //header_label->set_editable(false); // WARNING!! - Enabling this will mess with the theme. + //header_label->set_selection_enabled(true); + header_label->set_theme(_create_header_label_theme()); + layout_container->add_child(header_label); + + // Create RichTextLabel for the tooltip's body. + body_label = memnew(RichTextLabel); + body_label->set_use_bbcode(true); + body_label->set_selection_enabled(true); + body_label->set_custom_minimum_size(Size2(400, 45)); + body_label->set_focus_mode(Control::FOCUS_ALL); + //body_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); + body_label->set_theme(_create_body_label_theme()); + body_label->set_mouse_filter(Control::MOUSE_FILTER_STOP); + body_label->set_context_menu_enabled(true); + //body_label->set_fit_content(true); // WARNING!! - Enabling this will cause issues in _update_tooltip_size(). + body_label->hide(); + layout_container->add_child(body_label); + + float tooltip_delay_time = ProjectSettings::get_singleton()->get("gui/timers/tooltip_delay_sec"); + tooltip_delay = memnew(Timer); + tooltip_delay->set_one_shot(true); + tooltip_delay->set_wait_time(tooltip_delay_time); + add_child(tooltip_delay); + + tooltip_delay->connect("timeout", callable_mp(this, &SymbolTooltip::_on_tooltip_delay_timeout)); + + mouse_inside = false; + + // Connect the tooltip's update function to the mouse motion signal. + // connect("mouse_motion", callable_mp(this, &SymbolTooltip::_update_symbol_tooltip)); +} + +SymbolTooltip::~SymbolTooltip() { +} + +void SymbolTooltip::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_PROCESS: { + // Note: Child components prevent NOTIFICATION_MOUSE_ENTER and NOTIFICATION_MOUSE_EXIT from working properly. + // Get the local mouse position + Point2i local_mouse_position = get_global_mouse_position() - get_global_position(); + + // Check if it's within the rect of the PanelContainer + Rect2 tooltip_rect = Rect2(Point2i(0, 0), get_size()); + if (tooltip_rect.has_point(local_mouse_position)) { + if (is_visible() && !mouse_inside) { + // Mouse just entered + mouse_inside = true; + } + } else { + if (mouse_inside) { + // Mouse just exited + mouse_inside = false; + if (is_visible()) { + _close_tooltip(); + } + } + } + } break; + } +} + +void SymbolTooltip::_on_tooltip_delay_timeout() { + print_line("size: " + get_size() + ", vbox_size: " + layout_container->get_size() + ", header_size: " + header_label->get_size() + ", body_size: " + body_label->get_size()); + + show(); +} + +void SymbolTooltip::_close_tooltip() { + tooltip_delay->stop(); + hide(); +} + +void SymbolTooltip::update_symbol_tooltip(const Point2i &p_mouse_pos, const Ref