Merge pull request #92080 from Nodragem/make-dropping-tooltip-consistent

Make resource drop tooltip consistent between 2D and 3D
This commit is contained in:
Thaddeus Crews
2026-03-06 12:40:18 -06:00
4 changed files with 255 additions and 174 deletions
+178 -119
View File
@@ -102,6 +102,7 @@
#include "scene/gui/center_container.h"
#include "scene/gui/color_picker.h"
#include "scene/gui/flow_container.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/separator.h"
#include "scene/gui/split_container.h"
#include "scene/gui/subviewport_container.h"
@@ -2645,9 +2646,6 @@ void Node3DEditorViewport::_notification(int p_what) {
_update_centered_labels();
message_time = MIN(message_time, 0.001); // Make it disappear.
Key key = OS::prefer_meta_over_ctrl() ? Key::META : Key::CTRL;
preview_material_label_desc->set_text(vformat(TTR("Drag and drop to override the material of any geometry node.\nHold %s when dropping to override a specific surface."), find_keycode_name(key)));
const int item_count = display_submenu->get_item_count();
for (int i = 0; i < item_count; i++) {
const Array item_data = display_submenu->get_item_metadata(i);
@@ -3046,6 +3044,7 @@ void Node3DEditorViewport::_notification(int p_what) {
view_display_menu->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHlDarkBackground")));
preview_camera->set_button_icon(get_editor_theme_icon(SNAME("Camera3DDarkBackground")));
Control *gui_base = EditorNode::get_singleton()->get_gui_base();
const Ref<StyleBox> &information_3d_stylebox = gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles));
@@ -3065,6 +3064,7 @@ void Node3DEditorViewport::_notification(int p_what) {
info_panel->add_theme_style_override(SceneStringName(panel), information_3d_stylebox);
override_label_colors(info_label);
tooltip_panel->add_theme_style_override(CoreStringName(normal), information_3d_stylebox);
frame_time_panel->add_theme_style_override(SceneStringName(panel), information_3d_stylebox);
// Set a minimum width to prevent the width from changing all the time
@@ -4638,12 +4638,13 @@ void Node3DEditorViewport::_create_preview_node(const Vector<String> &files) con
}
if (add_preview) {
EditorNode::get_singleton()->get_scene_root()->add_child(preview_node);
*preview_bounds = _calculate_spatial_bounds(preview_node);
}
*preview_bounds = _calculate_spatial_bounds(preview_node);
}
void Node3DEditorViewport::_remove_preview_node() {
tooltip_panel->hide();
set_message("");
if (preview_node->get_parent()) {
for (int i = preview_node->get_child_count() - 1; i >= 0; i--) {
@@ -4744,8 +4745,7 @@ void Node3DEditorViewport::_reset_preview_material() const {
}
void Node3DEditorViewport::_remove_preview_material() {
preview_material_label->hide();
preview_material_label_desc->hide();
tooltip_panel->hide();
spatial_editor->set_preview_material(Ref<Material>());
spatial_editor->set_preview_reset_material(Ref<Material>());
@@ -4946,116 +4946,176 @@ void Node3DEditorViewport::_perform_drop_data() {
}
}
void Node3DEditorViewport::_show_tooltip(const String &p_title, const String &p_description) const {
tooltip_panel->set_text(
vformat("[font_size=%s][b][color=%s]%s[/color][/b][/font_size]\n%s",
get_theme_default_font_size() + 2,
get_theme_color(SNAME("accent_color"), EditorStringName(Editor)).to_html(false),
p_title, p_description));
tooltip_panel->show();
}
bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
if (p_point == Vector2(Math::INF, Math::INF)) {
tooltip_panel->hide();
return false;
}
preview_node_viewport_pos = p_point;
bool can_instantiate = false;
bool is_cyclical_dep = false;
String error_file;
if (!preview_node->is_inside_tree() && spatial_editor->get_preview_material().is_null()) {
Dictionary d = p_data;
if (d.has("type") && (String(d["type"]) == "files")) {
Vector<String> files = d["files"];
// Track whether a type other than PackedScene is valid to stop checking them and only
// continue to check if the rest of the scenes are valid (don't have cyclic dependencies).
bool is_other_valid = false;
// Check if at least one of the dragged files is a mesh, material, texture or scene.
for (int i = 0; i < files.size(); i++) {
const String &res_type = ResourceLoader::get_resource_type(files[i]);
bool is_scene = ClassDB::is_parent_class(res_type, "PackedScene");
bool is_mesh = ClassDB::is_parent_class(res_type, "Mesh");
bool is_material = ClassDB::is_parent_class(res_type, "Material");
bool is_texture = ClassDB::is_parent_class(res_type, "Texture");
bool is_audio = ClassDB::is_parent_class(res_type, "AudioStream");
if (is_mesh || is_scene || is_material || is_texture || is_audio) {
Ref<Resource> res = ResourceLoader::load(files[i]);
if (res.is_null()) {
continue;
}
Ref<PackedScene> scn = res;
Ref<Mesh> mesh = res;
Ref<Material> mat = res;
Ref<Texture2D> tex = res;
Ref<AudioStream> audio = res;
if (scn.is_valid()) {
Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
if (!instantiated_scene) {
continue;
}
Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {
memdelete(instantiated_scene);
can_instantiate = false;
is_cyclical_dep = true;
error_file = files[i].get_file();
break;
}
memdelete(instantiated_scene);
} else if (!is_other_valid && mat.is_valid()) {
Ref<BaseMaterial3D> base_mat = res;
Ref<ShaderMaterial> shader_mat = res;
if (base_mat.is_null() && shader_mat.is_null()) {
continue;
}
spatial_editor->set_preview_material(mat);
is_other_valid = true;
continue;
} else if (!is_other_valid && mesh.is_valid()) {
// Let the mesh pass.
is_other_valid = true;
} else if (!is_other_valid && tex.is_valid()) {
Ref<StandardMaterial3D> new_mat = memnew(StandardMaterial3D);
new_mat->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, tex);
spatial_editor->set_preview_material(new_mat);
is_other_valid = true;
continue;
} else if (!is_other_valid && audio.is_valid()) {
is_other_valid = true;
} else {
continue;
}
can_instantiate = true;
}
}
if (can_instantiate) {
_create_preview_node(files);
preview_node->hide();
}
}
} else {
if (preview_node->is_inside_tree()) {
can_instantiate = true;
}
}
if (is_cyclical_dep) {
set_message(vformat(TTR("Can't instantiate: %s."), vformat(TTR("Circular dependency found at %s"), error_file)));
Dictionary d = p_data;
if (!d.has("type") || String(d["type"]) != "files") {
tooltip_panel->hide();
return false;
}
Vector<String> files = d["files"];
if (can_instantiate) {
update_preview_node = true;
return true;
}
// If we already have a preview material or preview node,
// We just need to update them.
if (spatial_editor->get_preview_material().is_valid()) {
preview_material_label->show();
preview_material_label_desc->show();
ObjectID new_preview_material_target = _select_ray(p_point);
return _apply_preview_material(new_preview_material_target, p_point);
}
return false;
if (preview_node->is_inside_tree()) {
preview_node_viewport_pos = p_point;
update_preview_node = true;
return true;
}
// If we don't already have a preview material or preview node,
// it means that this is the first time we are visiting this function.
// In that case, we need to check that the file(s) are droppable.
bool is_cyclical_dep = false;
String error_file;
enum {
SCENE = 1 << 0,
TEXTURE = 1 << 1,
AUDIO = 1 << 2,
MESH = 1 << 3,
MATERIAL = 1 << 4,
};
int instantiate_type = 0;
// Track whether a type other than PackedScene is valid to stop checking them and only
// continue to check if the rest of the scenes are valid (don't have cyclic dependencies).
bool is_other_valid = false;
// Check if at least one of the dragged files is a mesh, material, texture, or scene.
for (int i = 0; i < files.size(); i++) {
const String &res_type = ResourceLoader::get_resource_type(files[i]);
bool is_scene = ClassDB::is_parent_class(res_type, "PackedScene");
bool is_mesh = ClassDB::is_parent_class(res_type, "Mesh");
bool is_material = ClassDB::is_parent_class(res_type, "Material");
bool is_texture = ClassDB::is_parent_class(res_type, "Texture");
bool is_audio = ClassDB::is_parent_class(res_type, "AudioStream");
if (is_mesh || is_scene || is_material || is_texture || is_audio) {
Ref<Resource> res = ResourceLoader::load(files[i]);
if (res.is_null()) {
continue;
}
Ref<PackedScene> scn = res;
Ref<Mesh> mesh = res;
Ref<Material> mat = res;
Ref<Texture2D> tex = res;
Ref<AudioStream> audio = res;
if (scn.is_valid()) {
Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE);
if (!instantiated_scene) {
continue;
}
Node *edited_scene = EditorNode::get_singleton()->get_edited_scene();
if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {
memdelete(instantiated_scene);
is_cyclical_dep = true;
error_file = files[i].get_file();
break;
}
memdelete(instantiated_scene);
instantiate_type |= SCENE;
} else if (!is_other_valid && mat.is_valid()) {
Ref<BaseMaterial3D> base_mat = res;
Ref<ShaderMaterial> shader_mat = res;
if (base_mat.is_null() && shader_mat.is_null()) {
continue;
}
spatial_editor->set_preview_material(mat);
is_other_valid = true;
instantiate_type |= MATERIAL;
continue;
} else if (!is_other_valid && mesh.is_valid()) {
// Let the mesh pass.
is_other_valid = true;
instantiate_type |= MESH;
} else if (!is_other_valid && tex.is_valid()) {
Ref<StandardMaterial3D> new_mat;
new_mat.instantiate();
new_mat->set_texture(BaseMaterial3D::TEXTURE_ALBEDO, tex);
spatial_editor->set_preview_material(new_mat);
is_other_valid = true;
instantiate_type |= TEXTURE;
continue;
} else if (!is_other_valid && audio.is_valid()) {
is_other_valid = true;
instantiate_type |= AUDIO;
} else {
continue;
}
}
}
String title = TTRN("Can't drop the file...", "Can't drop the files...", files.size());
if (is_cyclical_dep) {
_show_tooltip(title, vformat(TTR("Circular dependency found at %s."), error_file));
return false;
}
if (instantiate_type == 0) {
_show_tooltip(title, TTR("File format is not supported."));
return false;
}
// Only droppable file(s), on first frame, will make it to this point.
// Hence it is a good place to create the previews and tooltips.
_create_preview_node(files);
preview_node->hide();
String desc = "[ul]" +
TTRN("[b]Default:[/b] Add as sibling of selected node (except when root is selected).",
"[b]Default:[/b] Add as siblings of selected node (except when root is selected).",
files.size()) +
"\n" +
TTRN("[b]Hold Shift:[/b] Add as child of selected node.",
"[b]Hold Shift:[/b] Add as children of selected node.",
files.size()) +
"\n" +
TTRN("[b]Hold Alt:[/b] Add as child of root node.",
"[b]Hold Alt:[/b] Add as children of root node.",
files.size());
if (files.size() > 1) {
title = TTR("Dropping multiple files...");
} else if (instantiate_type & SCENE) {
title = TTR("Dropping a Scene file...");
} else if (instantiate_type & MESH) {
title = TTR("Dropping a Mesh file...");
} else if (instantiate_type & AUDIO) {
title = TTR("Dropping an Audio file...");
} else if (instantiate_type & MATERIAL || instantiate_type & TEXTURE) {
title = TTR("Dropping a Material...");
desc = "[ul]";
desc += vformat(TTR("[b]Default:[/b] Place in Geometry's Material Override slot.") +
"\n" + TTR("[b]Hold %s:[/b] Place in Mesh's Surface Material Override slot."),
keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL));
}
desc += "[/ul]";
_show_tooltip(title, desc);
return true;
}
void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
@@ -5916,22 +5976,21 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p
zoom_limit_label->hide();
bottom_center_vbox->add_child(zoom_limit_label);
preview_material_label = memnew(Label);
preview_material_label->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);
preview_material_label->set_offset(Side::SIDE_TOP, -70 * EDSCALE);
preview_material_label->set_text(TTRC("Overriding material..."));
preview_material_label->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1, 1));
preview_material_label->hide();
surface->add_child(preview_material_label);
preview_material_label_desc = memnew(Label);
preview_material_label_desc->set_focus_mode(FOCUS_ACCESSIBILITY);
preview_material_label_desc->set_anchors_and_offsets_preset(LayoutPreset::PRESET_BOTTOM_LEFT);
preview_material_label_desc->set_offset(Side::SIDE_TOP, -50 * EDSCALE);
preview_material_label_desc->add_theme_color_override(SceneStringName(font_color), Color(0.8, 0.8, 0.8, 1));
preview_material_label_desc->add_theme_constant_override("line_spacing", 0);
preview_material_label_desc->hide();
surface->add_child(preview_material_label_desc);
tooltip_panel = memnew(RichTextLabel);
vbox->add_child(tooltip_panel);
tooltip_panel->hide();
tooltip_panel->set_h_grow_direction(GROW_DIRECTION_BEGIN);
tooltip_panel->set_v_grow_direction(GROW_DIRECTION_BEGIN);
tooltip_panel->set_mouse_filter(MOUSE_FILTER_IGNORE);
tooltip_panel->set_focus_mode(FOCUS_ACCESSIBILITY);
tooltip_panel->set_use_bbcode(true);
tooltip_panel->set_fit_content(true);
tooltip_panel->set_scroll_active(false);
tooltip_panel->set_tab_size(1);
tooltip_panel->set_autowrap_mode(TextServer::AUTOWRAP_OFF);
tooltip_panel->set_anchors_and_offsets_preset(LayoutPreset::PRESET_TOP_LEFT);
tooltip_panel->add_theme_color_override(SceneStringName(font_color), Color(0.8f, 0.8f, 0.8f, 1));
tooltip_panel->add_theme_constant_override("paragraph_separation", 5);
frame_time_gradient = memnew(Gradient);
// The color is set when the theme changes.
+4 -2
View File
@@ -57,6 +57,7 @@ class Node3DEditorViewport;
class OptionButton;
class PanelContainer;
class ProceduralSkyMaterial;
class RichTextLabel;
class SubViewport;
class SubViewportContainer;
class VSeparator;
@@ -237,8 +238,7 @@ private:
Label *locked_label = nullptr;
Label *zoom_limit_label = nullptr;
Label *preview_material_label = nullptr;
Label *preview_material_label_desc = nullptr;
RichTextLabel *tooltip_panel = nullptr;
VBoxContainer *top_right_vbox = nullptr;
VBoxContainer *bottom_center_vbox = nullptr;
@@ -288,6 +288,8 @@ private:
float get_zfar() const;
float get_fov() const;
void _show_tooltip(const String &p_title, const String &p_description) const;
ObjectID clicked;
ObjectID material_target;
Vector<Node3D *> selection_results;
+69 -51
View File
@@ -63,6 +63,7 @@
#include "scene/gui/base_button.h"
#include "scene/gui/flow_container.h"
#include "scene/gui/grid_container.h"
#include "scene/gui/rich_text_label.h"
#include "scene/gui/separator.h"
#include "scene/gui/split_container.h"
#include "scene/gui/subviewport_container.h"
@@ -6186,7 +6187,7 @@ void CanvasItemEditorViewport::_on_select_texture_node_type(Object *selected) {
CheckBox *check = Object::cast_to<CheckBox>(selected);
String type = check->get_text();
texture_node_type_selector->set_title(vformat(TTR("Add %s"), type));
label->set_text(vformat(TTR("Adding %s..."), type));
tooltip_panel->set_text(vformat(TTR("Adding %s..."), type));
}
void CanvasItemEditorViewport::_on_change_type_confirmed() {
@@ -6206,6 +6207,7 @@ void CanvasItemEditorViewport::_on_change_type_closed() {
void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) const {
bool add_preview = false;
for (int i = 0; i < files.size(); i++) {
Ref<Resource> res = ResourceLoader::load(files[i]);
ERR_CONTINUE(res.is_null());
@@ -6249,6 +6251,7 @@ void CanvasItemEditorViewport::_remove_preview() {
canvas_item_editor->message = "";
canvas_item_editor->update_viewport();
}
tooltip_panel->hide();
if (preview_node->get_parent()) {
for (int i = preview_node->get_child_count() - 1; i >= 0; i--) {
Node *node = preview_node->get_child(i);
@@ -6256,9 +6259,6 @@ void CanvasItemEditorViewport::_remove_preview() {
preview_node->remove_child(node);
}
EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node);
label->hide();
label_desc->hide();
}
}
@@ -6516,13 +6516,23 @@ void CanvasItemEditorViewport::_perform_drop_data() {
}
}
void CanvasItemEditorViewport::_show_tooltip(const String &p_title, const String &p_description) const {
tooltip_panel->set_text(
vformat("[font_size=%s][b][color=%s]%s[/color][/b][/font_size]\n%s",
get_theme_default_font_size() + 2,
get_theme_color(SNAME("accent_color"), EditorStringName(Editor)).to_html(false),
p_title, p_description));
tooltip_panel->show();
}
bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Variant &p_data) const {
if (p_point == Vector2(Math::INF, Math::INF)) {
tooltip_panel->hide();
return false;
}
Dictionary d = p_data;
if (!d.has("type") || (String(d["type"]) != "files")) {
label->hide();
tooltip_panel->hide();
return false;
}
@@ -6542,9 +6552,9 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian
};
int instantiate_type = 0;
String error_message;
for (const String &path : files) {
const String &res_type = ResourceLoader::get_resource_type(path);
String error_message;
if (ClassDB::is_parent_class(res_type, "PackedScene")) {
Ref<PackedScene> scn = ResourceLoader::load(path);
@@ -6556,6 +6566,7 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian
}
if (edited_scene && !edited_scene->get_scene_file_path().is_empty() && _cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) {
error_message = vformat(TTR("Circular dependency found at %s."), path.get_file());
break;
}
memdelete(instantiated_scene);
instantiate_type |= SCENE;
@@ -6566,15 +6577,16 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian
if (ClassDB::is_parent_class(res_type, "AudioStream")) {
instantiate_type |= AUDIO;
}
}
if (!error_message.is_empty()) {
// TRANSLATORS: The placeholder is the error message.
canvas_item_editor->message = vformat(TTR("Can't instantiate: %s"), error_message);
canvas_item_editor->update_viewport();
return false;
}
String title = TTRN("Can't drop the file...", "Can't drop the files...", files.size());
if (!error_message.is_empty()) {
_show_tooltip(title, error_message);
canvas_item_editor->update_viewport();
return false;
}
if (instantiate_type == 0) {
_show_tooltip(title, TTR("This file format is not supported."));
return false;
}
@@ -6600,31 +6612,35 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian
}
canvas_item_editor->update_viewport();
if (instantiate_type & TEXTURE && instantiate_type & AUDIO) {
// TRANSLATORS: The placeholders are the types of nodes being instantiated.
label->set_text(vformat(TTR("Adding %s and %s..."), default_texture_node_type, "AudioStreamPlayer2D"));
} else {
String node_type;
if (instantiate_type & TEXTURE) {
node_type = default_texture_node_type;
} else if (instantiate_type & AUDIO) {
node_type = "AudioStreamPlayer2D";
}
if (!node_type.is_empty()) {
// TRANSLATORS: The placeholder is the type of node being instantiated.
label->set_text(vformat(TTR("Adding %s..."), node_type));
}
}
label->set_visible(instantiate_type & ~SCENE);
String desc = "[ul]" +
TTRN("[b]Default:[/b] Add as sibling of selected node (except when root is selected).",
"[b]Default:[/b] Add as siblings of selected node (except when root is selected).",
files.size()) +
"\n" +
TTRN("[b]Hold Shift:[/b] Add as child of selected node.",
"[b]Hold Shift:[/b] Add as children of selected node.",
files.size()) +
"\n" +
TTRN("[b]Hold Alt:[/b] Add as child of root node.",
"[b]Hold Alt:[/b] Add as children of root node.",
files.size());
String desc = TTR("Drag and drop to add as sibling of selected node (except when root is selected).") +
"\n" + TTR("Hold Shift when dropping to add as child of selected node.") +
"\n" + TTR("Hold Alt when dropping to add as child of root node.");
if (instantiate_type & TEXTURE) {
desc += "\n" + TTR("Hold Alt + Shift when dropping to add as different node type.");
if (files.size() > 1) {
title = TTR("Dropping multiple files...");
} else if (instantiate_type & SCENE) {
title = TTR("Dropping a Scene file...");
} else if (instantiate_type & AUDIO) {
title = TTR("Dropping an Audio file...");
} else if (instantiate_type & TEXTURE) {
// TRANSLATORS: The placeholders are the types of nodes being instantiated.
title = vformat(TTR("Dropping a Texture file as a %s node..."), default_texture_node_type);
}
label_desc->set_text(desc);
label_desc->show();
if (instantiate_type & TEXTURE) {
desc += "\n" + TTR("[b]Hold Alt + Shift:[/b] Add Texture as a different node type.");
}
desc += "[/ul]";
_show_tooltip(title, desc);
return true;
}
@@ -6703,8 +6719,9 @@ void CanvasItemEditorViewport::_update_theme() {
CheckBox *check = Object::cast_to<CheckBox>(btn);
check->set_button_icon(get_editor_theme_icon(check->get_text()));
}
label->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
Control *gui_base = EditorNode::get_singleton()->get_gui_base();
const Ref<StyleBox> &information_3d_stylebox = gui_base->get_theme_stylebox(SNAME("Information3dViewport"), EditorStringName(EditorStyles));
tooltip_panel->add_theme_style_override(CoreStringName(normal), information_3d_stylebox);
}
void CanvasItemEditorViewport::_notification(int p_what) {
@@ -6774,20 +6791,21 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_it
check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_texture_node_type).bind(check));
}
label = memnew(Label);
label->add_theme_color_override("font_shadow_color", Color(0, 0, 0, 1));
label->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE);
label->hide();
canvas_item_editor->get_controls_container()->add_child(label);
label_desc = memnew(Label);
label_desc->set_focus_mode(FOCUS_ACCESSIBILITY);
label_desc->add_theme_color_override(SceneStringName(font_color), Color(0.6f, 0.6f, 0.6f, 1));
label_desc->add_theme_color_override("font_shadow_color", Color(0.2f, 0.2f, 0.2f, 1));
label_desc->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE);
label_desc->add_theme_constant_override("line_spacing", 0);
label_desc->hide();
canvas_item_editor->get_controls_container()->add_child(label_desc);
tooltip_panel = memnew(RichTextLabel);
canvas_item_editor->get_controls_container()->add_child(tooltip_panel);
tooltip_panel->hide();
tooltip_panel->set_h_grow_direction(GROW_DIRECTION_BEGIN);
tooltip_panel->set_v_grow_direction(GROW_DIRECTION_BEGIN);
tooltip_panel->set_mouse_filter(MOUSE_FILTER_IGNORE);
tooltip_panel->set_focus_mode(FOCUS_ACCESSIBILITY);
tooltip_panel->set_use_bbcode(true);
tooltip_panel->set_fit_content(true);
tooltip_panel->set_scroll_active(false);
tooltip_panel->set_tab_size(0);
tooltip_panel->set_autowrap_mode(TextServer::AUTOWRAP_OFF);
tooltip_panel->set_anchors_and_offsets_preset(LayoutPreset::PRESET_TOP_LEFT);
tooltip_panel->add_theme_color_override(SceneStringName(font_color), Color(0.8f, 0.8f, 0.8f, 1));
tooltip_panel->add_theme_constant_override("paragraph_separation", 5);
RS::get_singleton()->canvas_set_disable_scale(true);
}
+4 -2
View File
@@ -45,6 +45,7 @@ class HScrollBar;
class HSplitContainer;
class MenuButton;
class PanelContainer;
class RichTextLabel;
class StyleBoxTexture;
class Timer;
class ViewPanner;
@@ -668,8 +669,7 @@ class CanvasItemEditorViewport : public Control {
Control *preview_node = nullptr;
AcceptDialog *accept = nullptr;
AcceptDialog *texture_node_type_selector = nullptr;
Label *label = nullptr;
Label *label_desc = nullptr;
RichTextLabel *tooltip_panel = nullptr;
Ref<ButtonGroup> button_group;
void _on_mouse_exit();
@@ -689,6 +689,8 @@ class CanvasItemEditorViewport : public Control {
void _show_texture_node_type_selector();
void _update_theme();
void _show_tooltip(const String &p_title, const String &p_description) const;
protected:
void _notification(int p_what);