diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index 25b6fcdfc17..9b6972dff89 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -532,7 +532,7 @@ class NodeTreeMainUpdater { if (node_tree_reference_lifetimes::analyse_reference_lifetimes(ntree)) { result.interface_changed = true; } - if (nodes::gizmos::update_tree_gizmo_propagation(ntree)) { + if (gizmos::update_tree_gizmo_propagation(ntree)) { result.interface_changed = true; } } diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index c8f9f8cb5df..e333d1ce35d 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -1035,6 +1035,9 @@ void UI_but_type_set_menu_from_pulldown(uiBut *but); */ void UI_but_color_set(uiBut *but, const uchar color[4]); +bool UI_but_is_color_gamma(uiBut &but); +const ColorManagedDisplay *UI_but_cm_display_get(uiBut &but); + /** * Set at hint that describes the expected value when empty. */ @@ -1919,6 +1922,22 @@ void UI_tooltip_text_field_add(uiTooltipData &data, */ void UI_tooltip_image_field_add(uiTooltipData &data, const uiTooltipImage &image_data); +void UI_tooltip_color_field_add(uiTooltipData &data, + const blender::float4 &color, + bool has_alpha, + bool is_gamma, + const ColorManagedDisplay *display, + uiTooltipColorID color_id); + +/** + * Add Python-related information to the tooltip. The caller is responsible for checking + * #USER_TOOLTIPS_PYTHON. + */ +void UI_tooltip_uibut_python_add(uiTooltipData &data, + bContext &C, + uiBut &but, + uiButExtraOpIcon *extra_icon); + /** * Recreate tool-tip (use to update dynamic tips) */ diff --git a/source/blender/editors/include/UI_interface_types.hh b/source/blender/editors/include/UI_interface_types.hh index 3a3fee58ffb..899c19d2a29 100644 --- a/source/blender/editors/include/UI_interface_types.hh +++ b/source/blender/editors/include/UI_interface_types.hh @@ -44,3 +44,8 @@ using uiButToolTipFunc = std::string (*)(bContext *C, void *argN, blender::Strin * Mostly useful when using #uiLayoutSetTooltipCustomFunc. */ using uiButToolTipCustomFunc = void (*)(bContext &C, uiTooltipData &data, uiBut *but, void *argN); + +namespace blender::ocio { +class Display; +} // namespace blender::ocio +using ColorManagedDisplay = blender::ocio::Display; diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc index 6f7eda49f41..1a72319ebf6 100644 --- a/source/blender/editors/interface/interface.cc +++ b/source/blender/editors/interface/interface.cc @@ -6087,6 +6087,16 @@ void UI_but_color_set(uiBut *but, const uchar color[4]) copy_v4_v4_uchar(but->col, color); } +const ColorManagedDisplay *UI_but_cm_display_get(uiBut &but) +{ + return ui_block_cm_display_get(but.block); +} + +bool UI_but_is_color_gamma(uiBut &but) +{ + return ui_but_is_color_gamma(&but); +} + void UI_but_placeholder_set(uiBut *but, const StringRef placeholder_text) { MEM_SAFE_FREE(but->placeholder); diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index b2f0b0b4cf9..2c98d1338bc 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -50,11 +50,6 @@ struct wmKeyConfig; struct wmOperatorType; struct wmTimer; -namespace blender::ocio { -class Display; -} // namespace blender::ocio -using ColorManagedDisplay = blender::ocio::Display; - /* ****************** general defines ************** */ #define RNA_NO_INDEX -1 diff --git a/source/blender/editors/interface/regions/interface_region_tooltip.cc b/source/blender/editors/interface/regions/interface_region_tooltip.cc index 025ce80b9a6..761f0316ee9 100644 --- a/source/blender/editors/interface/regions/interface_region_tooltip.cc +++ b/source/blender/editors/interface/regions/interface_region_tooltip.cc @@ -58,6 +58,7 @@ #include "GPU_immediate_util.hh" #include "GPU_state.hh" +#include "IMB_colormanagement.hh" #include "IMB_imbuf.hh" #include "IMB_imbuf_types.hh" #include "IMB_thumbs.hh" @@ -834,6 +835,125 @@ static std::string ui_tooltip_color_string(const blender::float4 &color, return fmt::format("{}: {:.3f} {:.3f} {:.3f}", TIP_(title), color[0], color[1], color[2]); }; +void UI_tooltip_color_field_add(uiTooltipData &data, + const blender::float4 &original_color, + const bool has_alpha, + const bool is_gamma, + const ColorManagedDisplay *display, + const uiTooltipColorID color_id) +{ + blender::float4 color = original_color; + if (!is_gamma && display) { + IMB_colormanagement_scene_linear_to_display_v3(color, display); + } + + const std::string hex_st = ui_tooltip_color_string(color, "Hex", has_alpha, true); + + const std::string rgba_st = ui_tooltip_color_string( + color, has_alpha ? "RGBA" : "RGB", has_alpha); + + float hsva[4]; + rgb_to_hsv_v(color, hsva); + hsva[3] = color[3]; + const std::string hsva_st = ui_tooltip_color_string(hsva, has_alpha ? "HSVA" : "HSV", has_alpha); + + const uiFontStyle *fs = &UI_style_get()->tooltip; + BLF_size(blf_mono_font, fs->points * UI_SCALE_FAC); + float w = BLF_width(blf_mono_font, hsva_st.c_str(), hsva_st.size()); + + uiTooltipImage image_data; + image_data.width = int(w); + image_data.height = int(w / (has_alpha ? 4.0f : 3.0f)); + image_data.ibuf = IMB_allocImBuf(image_data.width, image_data.height, 32, IB_byte_data); + image_data.border = true; + image_data.premultiplied = false; + + if (color[3] == 1.0f) { + /* No transparency so draw the entire area solid without checkerboard. */ + image_data.background = uiTooltipImageBackground::None; + IMB_rectfill_area(image_data.ibuf, color, 1, 1, image_data.width, image_data.height, display); + } + else { + image_data.background = uiTooltipImageBackground::Checkerboard_Fixed; + /* Draw one half with transparency. */ + IMB_rectfill_area(image_data.ibuf, + color, + image_data.width / 2, + 1, + image_data.width, + image_data.height, + display); + /* Draw the other half with a solid color. */ + color[3] = 1.0f; + IMB_rectfill_area( + image_data.ibuf, color, 1, 1, image_data.width / 2, image_data.height, display); + } + + UI_tooltip_text_field_add(data, {}, {}, UI_TIP_STYLE_SPACER, color_id, false); + UI_tooltip_text_field_add(data, {}, {}, UI_TIP_STYLE_SPACER, color_id, false); + UI_tooltip_image_field_add(data, image_data); + UI_tooltip_text_field_add(data, {}, {}, UI_TIP_STYLE_SPACER, color_id, false); + UI_tooltip_text_field_add(data, hex_st, {}, UI_TIP_STYLE_MONO, color_id, false); + UI_tooltip_text_field_add(data, {}, {}, UI_TIP_STYLE_SPACER, color_id, false); + UI_tooltip_text_field_add(data, rgba_st, {}, UI_TIP_STYLE_MONO, color_id, false); + UI_tooltip_text_field_add(data, hsva_st, {}, UI_TIP_STYLE_MONO, color_id, false); + + /* Tooltip now owns a copy of the ImBuf, so we can delete ours. */ + IMB_freeImBuf(image_data.ibuf); +} + +void UI_tooltip_uibut_python_add(uiTooltipData &data, + bContext &C, + uiBut &but, + uiButExtraOpIcon *extra_icon) +{ + wmOperatorType *optype = extra_icon ? UI_but_extra_operator_icon_optype_get(extra_icon) : + but.optype; + PropertyRNA *rnaprop = extra_icon ? nullptr : but.rnaprop; + std::string rna_struct = UI_but_string_get_rna_struct_identifier(but); + std::string rna_prop = UI_but_string_get_rna_property_identifier(but); + + if (optype && !rnaprop) { + PointerRNA *opptr = extra_icon ? UI_but_extra_operator_icon_opptr_get(extra_icon) : + /* Allocated when needed, the button owns it. */ + UI_but_operator_ptr_ensure(&but); + + /* So the context is passed to field functions (some Python field functions use it). */ + WM_operator_properties_sanitize(opptr, false); + + std::string str = ui_tooltip_text_python_from_op(&C, optype, opptr); + + /* Operator info. */ + UI_tooltip_text_field_add(data, + fmt::format(fmt::runtime(TIP_("Python: {}")), str), + {}, + UI_TIP_STYLE_MONO, + UI_TIP_LC_PYTHON, + true); + } + + if (!optype && !rna_struct.empty()) { + { + UI_tooltip_text_field_add( + data, + rna_prop.empty() ? + fmt::format(fmt::runtime(TIP_("Python: {}")), rna_struct) : + fmt::format(fmt::runtime(TIP_("Python: {}.{}")), rna_struct, rna_prop), + {}, + UI_TIP_STYLE_MONO, + UI_TIP_LC_PYTHON, + (data.fields.size() > 0)); + } + + if (but.rnapoin.owner_id) { + std::optional str = rnaprop ? RNA_path_full_property_py_ex( + &but.rnapoin, rnaprop, but.rnaindex, true) : + RNA_path_full_struct_py(&but.rnapoin); + UI_tooltip_text_field_add(data, str.value_or(""), {}, UI_TIP_STYLE_MONO, UI_TIP_LC_PYTHON); + } + } +} + static std::unique_ptr ui_tooltip_data_from_button_or_extra_icon( bContext *C, uiBut *but, uiButExtraOpIcon *extra_icon, const bool is_quick_tip) { @@ -1128,47 +1248,12 @@ static std::unique_ptr ui_tooltip_data_from_button_or_extra_icon( } } - if (U.flag & USER_TOOLTIPS_PYTHON && optype && !rnaprop) { - PointerRNA *opptr = extra_icon ? UI_but_extra_operator_icon_opptr_get(extra_icon) : - /* Allocated when needed, the button owns it. */ - UI_but_operator_ptr_ensure(but); - - /* So the context is passed to field functions (some Python field functions use it). */ - WM_operator_properties_sanitize(opptr, false); - - std::string str = ui_tooltip_text_python_from_op(C, optype, opptr); - - /* Operator info. */ - UI_tooltip_text_field_add(*data, - fmt::format(fmt::runtime(TIP_("Python: {}")), str), - {}, - UI_TIP_STYLE_MONO, - UI_TIP_LC_PYTHON, - true); - } - - if ((U.flag & USER_TOOLTIPS_PYTHON) && !optype && !rna_struct.empty()) { - { - UI_tooltip_text_field_add( - *data, - rna_prop.empty() ? - fmt::format(fmt::runtime(TIP_("Python: {}")), rna_struct) : - fmt::format(fmt::runtime(TIP_("Python: {}.{}")), rna_struct, rna_prop), - {}, - UI_TIP_STYLE_MONO, - UI_TIP_LC_PYTHON, - (data->fields.size() > 0)); - } - - if (but->rnapoin.owner_id) { - std::optional str = rnaprop ? RNA_path_full_property_py_ex( - &but->rnapoin, rnaprop, but->rnaindex, true) : - RNA_path_full_struct_py(&but->rnapoin); - UI_tooltip_text_field_add(*data, str.value_or(""), {}, UI_TIP_STYLE_MONO, UI_TIP_LC_PYTHON); - } + if (U.flag & USER_TOOLTIPS_PYTHON) { + UI_tooltip_uibut_python_add(*data, *C, *but, extra_icon); } if (but->type == UI_BTYPE_COLOR) { + const ColorManagedDisplay *display = UI_but_cm_display_get(*but); float color[4]; ui_but_v3_get(but, color); @@ -1183,66 +1268,8 @@ static std::unique_ptr ui_tooltip_data_from_button_or_extra_icon( } } - if (!ui_but_is_color_gamma(but)) { - ui_block_cm_to_display_space_v3(but->block, color); - } - - const std::string hex_st = ui_tooltip_color_string(color, "Hex", has_alpha, true); - - const std::string rgba_st = ui_tooltip_color_string( - color, has_alpha ? "RGBA" : "RGB", has_alpha); - - float hsva[4]; - rgb_to_hsv_v(color, hsva); - hsva[3] = color[3]; - const std::string hsva_st = ui_tooltip_color_string( - hsva, has_alpha ? "HSVA" : "HSV", has_alpha); - - const uiFontStyle *fs = &UI_style_get()->tooltip; - BLF_size(blf_mono_font, fs->points * UI_SCALE_FAC); - float w = BLF_width(blf_mono_font, hsva_st.c_str(), hsva_st.size()); - - uiTooltipImage image_data; - image_data.width = int(w); - image_data.height = int(w / (has_alpha ? 4.0f : 3.0f)); - image_data.ibuf = IMB_allocImBuf(image_data.width, image_data.height, 32, IB_byte_data); - image_data.border = true; - image_data.premultiplied = false; - - const ColorManagedDisplay *display = ui_block_cm_display_get(but->block); - if (color[3] == 1.0f) { - /* No transparency so draw the entire area solid without checkerboard. */ - image_data.background = uiTooltipImageBackground::None; - IMB_rectfill_area( - image_data.ibuf, color, 1, 1, image_data.width, image_data.height, display); - } - else { - image_data.background = uiTooltipImageBackground::Checkerboard_Fixed; - /* Draw one half with transparency. */ - IMB_rectfill_area(image_data.ibuf, - color, - image_data.width / 2, - 1, - image_data.width, - image_data.height, - display); - /* Draw the other half with a solid color. */ - color[3] = 1.0f; - IMB_rectfill_area( - image_data.ibuf, color, 1, 1, image_data.width / 2, image_data.height, display); - } - - UI_tooltip_text_field_add(*data, {}, {}, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL, false); - UI_tooltip_text_field_add(*data, {}, {}, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL, false); - UI_tooltip_image_field_add(*data, image_data); - UI_tooltip_text_field_add(*data, {}, {}, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL, false); - UI_tooltip_text_field_add(*data, hex_st, {}, UI_TIP_STYLE_MONO, UI_TIP_LC_NORMAL, false); - UI_tooltip_text_field_add(*data, {}, {}, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL, false); - UI_tooltip_text_field_add(*data, rgba_st, {}, UI_TIP_STYLE_MONO, UI_TIP_LC_NORMAL, false); - UI_tooltip_text_field_add(*data, hsva_st, {}, UI_TIP_STYLE_MONO, UI_TIP_LC_NORMAL, false); - - /* Tooltip now owns a copy of the ImBuf, so we can delete ours. */ - IMB_freeImBuf(image_data.ibuf); + UI_tooltip_color_field_add( + *data, color, has_alpha, ui_but_is_color_gamma(but), display, UI_TIP_LC_NORMAL); } /* If the last field is a spacer, remove it. */ diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt index 38fb609fd0c..232361f55fd 100644 --- a/source/blender/editors/space_node/CMakeLists.txt +++ b/source/blender/editors/space_node/CMakeLists.txt @@ -37,6 +37,7 @@ set(SRC node_relationships.cc node_select.cc node_shader_preview.cc + node_socket_tooltip.cc node_sync_sockets.cc node_templates.cc node_view.cc diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index b4d305f8681..8c175961158 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -19,7 +19,6 @@ #include "DNA_screen_types.h" #include "DNA_space_types.h" #include "DNA_text_types.h" -#include "DNA_texture_types.h" #include "DNA_world_types.h" #include "BLI_array.hh" @@ -28,8 +27,6 @@ #include "BLI_function_ref.hh" #include "BLI_listbase.h" #include "BLI_map.hh" -#include "BLI_math_matrix.hh" -#include "BLI_math_quaternion.hh" #include "BLI_set.hh" #include "BLI_span.hh" #include "BLI_string.h" @@ -49,7 +46,6 @@ #include "BKE_main.hh" #include "BKE_main_invariants.hh" #include "BKE_node.hh" -#include "BKE_node_enum.hh" #include "BKE_node_legacy_types.hh" #include "BKE_node_runtime.hh" #include "BKE_node_tree_update.hh" @@ -58,7 +54,6 @@ #include "BKE_scene.hh" #include "BKE_scene_runtime.hh" #include "BKE_screen.hh" -#include "BKE_type_conversions.hh" #include "IMB_imbuf.hh" @@ -97,7 +92,6 @@ #include "NOD_geometry_nodes_log.hh" #include "NOD_node_declaration.hh" #include "NOD_node_extra_info.hh" -#include "NOD_socket_declarations_geometry.hh" #include "GEO_fillet_curves.hh" @@ -232,10 +226,6 @@ void tag_update_id(ID *id) } } -static std::string node_socket_get_tooltip(const SpaceNode *snode, - const bNodeTree &ntree, - const bNodeSocket &socket); - static const char *node_socket_get_translation_context(const bNodeSocket &socket) { /* The node is not explicitly defined. */ @@ -1451,14 +1441,16 @@ static void node_socket_tooltip_set(uiBlock &block, 0, 0, std::nullopt); - UI_but_func_tooltip_set( + + UI_but_func_tooltip_custom_set( but, - [](bContext *C, void *argN, const StringRef /*tip*/) { - const SpaceNode &snode = *CTX_wm_space_node(C); + [](bContext &C, uiTooltipData &tip, uiBut *but, void *argN) { + const SpaceNode &snode = *CTX_wm_space_node(&C); const bNodeTree &ntree = *snode.edittree; const int index_in_tree = POINTER_AS_INT(argN); ntree.ensure_topology_cache(); - return node_socket_get_tooltip(&snode, ntree, *ntree.all_sockets()[index_in_tree]); + const bNodeSocket &socket = *ntree.all_sockets()[index_in_tree]; + build_socket_tooltip(tip, C, but, ntree, socket); }, POINTER_FROM_INT(socket_index_in_tree), nullptr); @@ -1507,735 +1499,17 @@ void node_socket_color_get(const bContext &C, sock.typeinfo->draw_color((bContext *)&C, &ptr, &node_ptr, r_color); } -static void create_inspection_string_for_generic_value(const bNodeSocket &socket, - const GPointer value, - fmt::memory_buffer &buf) -{ - auto id_to_inspection_string = [&](const ID *id, const short idcode) { - fmt::format_to(fmt::appender(buf), "{}", id ? id->name + 2 : TIP_("None")); - fmt::format_to(fmt::appender(buf), " ("); - fmt::format_to(fmt::appender(buf), "{}", TIP_(BKE_idtype_idcode_to_name(idcode))); - fmt::format_to(fmt::appender(buf), ")"); - }; - - const CPPType &value_type = *value.type(); - const void *buffer = value.get(); - if (value_type.is()) { - id_to_inspection_string(*static_cast(buffer), ID_OB); - return; - } - if (value_type.is()) { - id_to_inspection_string(*static_cast(buffer), ID_MA); - return; - } - if (value_type.is()) { - id_to_inspection_string(*static_cast(buffer), ID_TE); - return; - } - if (value_type.is()) { - id_to_inspection_string(*static_cast(buffer), ID_IM); - return; - } - if (value_type.is()) { - id_to_inspection_string(*static_cast(buffer), ID_GR); - return; - } - - const CPPType &socket_type = *socket.typeinfo->base_cpp_type; - - if (socket.type == SOCK_MENU) { - if (!value_type.is()) { - return; - } - const int item_identifier = *static_cast(buffer); - const auto *socket_storage = socket.default_value_typed(); - if (!socket_storage->enum_items) { - return; - } - if (socket_storage->has_conflict()) { - return; - } - const bke::RuntimeNodeEnumItem *enum_item = - socket_storage->enum_items->find_item_by_identifier(item_identifier); - if (!enum_item) { - return; - } - fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("{} (Menu)")), enum_item->name); - return; - } - - const bke::DataTypeConversions &convert = bke::get_implicit_type_conversions(); - if (value_type != socket_type) { - if (!convert.is_convertible(value_type, socket_type)) { - return; - } - } - BUFFER_FOR_CPP_TYPE_VALUE(socket_type, socket_value); - /* This will just copy the value if the types are equal. */ - convert.convert_to_uninitialized(value_type, socket_type, buffer, socket_value); - BLI_SCOPED_DEFER([&]() { socket_type.destruct(socket_value); }); - - if (socket_type.is()) { - fmt::format_to( - fmt::appender(buf), fmt::runtime(TIP_("{} (Integer)")), *static_cast(socket_value)); - } - else if (socket_type.is()) { - const float float_value = *static_cast(socket_value); - /* Above that threshold floats can't represent fractions anymore. */ - if (std::abs(float_value) > (1 << 24)) { - /* Use higher precision to display correct integer value instead of one that is rounded to - * fewer significant digits. */ - fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("{:.10} (Float)")), float_value); - } - else { - fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("{} (Float)")), float_value); - } - } - else if (socket_type.is()) { - const blender::float3 &vector = *static_cast(socket_value); - fmt::format_to(fmt::appender(buf), - fmt::runtime(TIP_("({}, {}, {}) (Vector)")), - vector.x, - vector.y, - vector.z); - } - else if (socket_type.is()) { - const blender::ColorGeometry4f &color = *static_cast(socket_value); - fmt::format_to(fmt::appender(buf), - fmt::runtime(TIP_("({}, {}, {}, {}) (Color)")), - color.r, - color.g, - color.b, - color.a); - } - else if (socket_type.is()) { - const math::Quaternion &rotation = *static_cast(socket_value); - const math::EulerXYZ euler = math::to_euler(rotation); - fmt::format_to(fmt::appender(buf), - ("({}" BLI_STR_UTF8_DEGREE_SIGN ", {}" BLI_STR_UTF8_DEGREE_SIGN - ", {}" BLI_STR_UTF8_DEGREE_SIGN ")"), - euler.x().degree(), - euler.y().degree(), - euler.z().degree()); - fmt::format_to(fmt::appender(buf), "{}", TIP_("(Rotation)")); - } - else if (socket_type.is()) { - fmt::format_to(fmt::appender(buf), - fmt::runtime(TIP_("{} (Boolean)")), - ((*static_cast(socket_value)) ? TIP_("True") : TIP_("False"))); - } - else if (socket_type.is()) { - /* Transpose to be able to print row by row. */ - const float4x4 value = math::transpose(*static_cast(socket_value)); - std::stringstream ss; - ss << value[0] << ",\n"; - ss << value[1] << ",\n"; - ss << value[2] << ",\n"; - ss << value[3] << ",\n"; - buf.append(ss.str()); - fmt::format_to(fmt::appender(buf), "{}", TIP_("(Matrix)")); - } -} - -static void create_inspection_string_for_field_info(const bNodeSocket &socket, - const geo_log::FieldInfoLog &value_log, - fmt::memory_buffer &buf) -{ - const CPPType &socket_type = *socket.typeinfo->base_cpp_type; - const Span input_tooltips = value_log.input_tooltips; - - if (input_tooltips.is_empty()) { - /* Should have been logged as constant value. */ - BLI_assert_unreachable(); - fmt::format_to(fmt::appender(buf), "{}", TIP_("Value has not been logged")); - } - else { - if (socket_type.is()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Integer field based on:")); - } - else if (socket_type.is()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Float field based on:")); - } - else if (socket_type.is()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Vector field based on:")); - } - else if (socket_type.is()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Boolean field based on:")); - } - else if (socket_type.is()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("String field based on:")); - } - else if (socket_type.is()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Color field based on:")); - } - else if (socket_type.is()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Rotation field based on:")); - } - fmt::format_to(fmt::appender(buf), "\n"); - - for (const int i : input_tooltips.index_range()) { - const blender::StringRef tooltip = input_tooltips[i]; - fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("\u2022 {}")), TIP_(tooltip)); - if (i < input_tooltips.size() - 1) { - fmt::format_to(fmt::appender(buf), ".\n"); - } - } - } -} - -static void create_inspection_string_for_geometry_info(const geo_log::GeometryInfoLog &value_log, - fmt::memory_buffer &buf) -{ - auto to_string = [](int value) { - char str[BLI_STR_FORMAT_INT32_GROUPED_SIZE]; - BLI_str_format_int_grouped(str, value); - return std::string(str); - }; - - if (value_log.grid_info) { - const geo_log::GeometryInfoLog::GridInfo &grid_info = *value_log.grid_info; - fmt::format_to( - fmt::appender(buf), "{}", grid_info.is_empty ? TIP_("Empty Grid") : TIP_("\u2022 Grid")); - return; - } - - Span component_types = value_log.component_types; - if (component_types.is_empty()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Empty Geometry")); - return; - } - - fmt::format_to(fmt::appender(buf), "{}", TIP_("Geometry:")); - if (!value_log.name.empty()) { - fmt::format_to(fmt::appender(buf), " \"{}\"", value_log.name); - } - fmt::format_to(fmt::appender(buf), "\n"); - for (bke::GeometryComponent::Type type : component_types) { - switch (type) { - case bke::GeometryComponent::Type::Mesh: { - const geo_log::GeometryInfoLog::MeshInfo &mesh_info = *value_log.mesh_info; - fmt::format_to(fmt::appender(buf), - fmt::runtime(TIP_("\u2022 Mesh: {} vertices, {} edges, {} faces")), - to_string(mesh_info.verts_num), - to_string(mesh_info.edges_num), - to_string(mesh_info.faces_num)); - break; - } - case bke::GeometryComponent::Type::PointCloud: { - const geo_log::GeometryInfoLog::PointCloudInfo &pointcloud_info = - *value_log.pointcloud_info; - fmt::format_to(fmt::appender(buf), - fmt::runtime(TIP_("\u2022 Point Cloud: {} points")), - to_string(pointcloud_info.points_num)); - break; - } - case bke::GeometryComponent::Type::Curve: { - const geo_log::GeometryInfoLog::CurveInfo &curve_info = *value_log.curve_info; - fmt::format_to(fmt::appender(buf), - fmt::runtime(TIP_("\u2022 Curve: {} points, {} splines")), - to_string(curve_info.points_num), - to_string(curve_info.splines_num)); - break; - } - case bke::GeometryComponent::Type::Instance: { - const geo_log::GeometryInfoLog::InstancesInfo &instances_info = *value_log.instances_info; - fmt::format_to(fmt::appender(buf), - fmt::runtime(TIP_("\u2022 Instances: {}")), - to_string(instances_info.instances_num)); - break; - } - case bke::GeometryComponent::Type::Volume: { - const geo_log::GeometryInfoLog::VolumeInfo &volume_info = *value_log.volume_info; - fmt::format_to(fmt::appender(buf), - fmt::runtime(TIP_("\u2022 Volume: {} grids")), - volume_info.grids_num); - break; - } - case bke::GeometryComponent::Type::Edit: { - if (value_log.edit_data_info.has_value()) { - const geo_log::GeometryInfoLog::EditDataInfo &edit_info = *value_log.edit_data_info; - fmt::format_to(fmt::appender(buf), - fmt::runtime(TIP_("\u2022 Edit: {}, {}, {}")), - edit_info.has_deformed_positions ? TIP_("positions") : - TIP_("no positions"), - edit_info.has_deform_matrices ? TIP_("matrices") : TIP_("no matrices"), - edit_info.gizmo_transforms_num > 0 ? TIP_("gizmos") : TIP_("no gizmos")); - } - break; - } - case bke::GeometryComponent::Type::GreasePencil: { - const geo_log::GeometryInfoLog::GreasePencilInfo &grease_pencil_info = - *value_log.grease_pencil_info; - fmt::format_to(fmt::appender(buf), - fmt::runtime(TIP_("\u2022 Grease Pencil: {} layers")), - to_string(grease_pencil_info.layers_num)); - break; - } - } - if (type != component_types.last()) { - fmt::format_to(fmt::appender(buf), ".\n"); - } - } -} - -static void create_inspection_string_for_geometry_socket(fmt::memory_buffer &buf, - const nodes::decl::Geometry *socket_decl) -{ - /* If the geometry declaration is null, as is the case for input to group output, - * or it is an output socket don't show supported types. */ - if (socket_decl == nullptr || socket_decl->in_out == SOCK_OUT) { - return; - } - - Span supported_types = socket_decl->supported_types(); - if (supported_types.is_empty()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Supported: All Types")); - return; - } - - fmt::format_to(fmt::appender(buf), "{}", TIP_("Supported: ")); - for (bke::GeometryComponent::Type type : supported_types) { - switch (type) { - case bke::GeometryComponent::Type::Mesh: { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Mesh")); - break; - } - case bke::GeometryComponent::Type::PointCloud: { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Point Cloud")); - break; - } - case bke::GeometryComponent::Type::Curve: { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Curve")); - break; - } - case bke::GeometryComponent::Type::Instance: { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Instances")); - break; - } - case bke::GeometryComponent::Type::Volume: { - fmt::format_to(fmt::appender(buf), "{}", CTX_TIP_(BLT_I18NCONTEXT_ID_ID, "Volume")); - break; - } - case bke::GeometryComponent::Type::Edit: { - break; - } - case bke::GeometryComponent::Type::GreasePencil: { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Grease Pencil")); - break; - } - } - if (type != supported_types.last()) { - fmt::format_to(fmt::appender(buf), ", "); - } - } -} - -static void create_inspection_string_for_bundle(const geo_log::BundleValueLog &value_log, - fmt::memory_buffer &buf) -{ - if (value_log.items.is_empty()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Empty Bundle")); - return; - } - fmt::format_to(fmt::appender(buf), "{}", TIP_("Bundle values:\n")); - for (const geo_log::BundleValueLog::Item &item : value_log.items) { - fmt::format_to(fmt::appender(buf), - fmt::runtime("\u2022 \"{}\" ({})\n"), - item.key.identifiers().first(), - TIP_(item.type->label)); - } -} - -static void create_inspection_string_for_closure(const geo_log::ClosureValueLog &value_log, - fmt::memory_buffer &buf) -{ - if (value_log.inputs.is_empty() && value_log.outputs.is_empty()) { - fmt::format_to(fmt::appender(buf), "{}", TIP_("Empty Closure")); - } - if (!value_log.inputs.is_empty()) { - fmt::format_to(fmt::appender(buf), "{}:\n", TIP_("Inputs")); - for (const geo_log::ClosureValueLog::Item &item : value_log.inputs) { - fmt::format_to(fmt::appender(buf), - fmt::runtime("\u2022 {} ({})\n"), - item.key.identifiers().first(), - IFACE_(item.type->label)); - } - } - if (!value_log.outputs.is_empty()) { - fmt::format_to(fmt::appender(buf), "{}:\n", TIP_("Outputs")); - for (const geo_log::ClosureValueLog::Item &item : value_log.outputs) { - fmt::format_to(fmt::appender(buf), - fmt::runtime("\u2022 {} ({})\n"), - item.key.identifiers().first(), - IFACE_(item.type->label)); - } - } -} - -static void create_inspection_string_for_default_socket_value(const bNodeSocket &socket, - fmt::memory_buffer &buf) -{ - if (!socket.is_input()) { - return; - } - if (socket.is_multi_input()) { - return; - } - if (socket.owner_node().is_reroute()) { - return; - } - const Span connected_sockets = socket.directly_linked_sockets(); - if (!connected_sockets.is_empty() && !connected_sockets[0]->owner_node().is_dangling_reroute()) { - return; - } - if (const nodes::SocketDeclaration *socket_decl = socket.runtime->declaration) { - if (socket_decl->input_field_type == nodes::InputSocketFieldType::Implicit) { - return; - } - } - if (socket.typeinfo->base_cpp_type == nullptr) { - return; - } - - const CPPType &value_type = *socket.typeinfo->base_cpp_type; - BUFFER_FOR_CPP_TYPE_VALUE(value_type, socket_value); - socket.typeinfo->get_base_cpp_value(socket.default_value, socket_value); - create_inspection_string_for_generic_value(socket, GPointer(value_type, socket_value), buf); - value_type.destruct(socket_value); -} - -static std::optional create_description_inspection_string(const bNodeSocket &socket) -{ - if (socket.runtime->declaration == nullptr) { - if (socket.description[0]) { - return socket.description; - } - return std::nullopt; - } - const blender::nodes::SocketDeclaration &socket_decl = *socket.runtime->declaration; - blender::StringRefNull description = socket_decl.description; - if (description.is_empty()) { - return std::nullopt; - } - - return TIP_(description); -} - -static std::optional create_log_inspection_string(geo_log::GeoTreeLog *geo_tree_log, - const bNodeSocket &socket) -{ - if (geo_tree_log == nullptr) { - return std::nullopt; - } - if (socket.typeinfo->base_cpp_type == nullptr) { - return std::nullopt; - } - - geo_tree_log->ensure_socket_values(); - geo_log::ValueLog *value_log = geo_tree_log->find_socket_value_log(socket); - fmt::memory_buffer buf; - if (const geo_log::GenericValueLog *generic_value_log = - dynamic_cast(value_log)) - { - create_inspection_string_for_generic_value(socket, generic_value_log->value, buf); - } - else if (const geo_log::StringLog *string_log = dynamic_cast( - value_log)) - { - if (string_log->truncated) { - fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("{}... (String)")), string_log->value); - } - else { - fmt::format_to(fmt::appender(buf), fmt::runtime(TIP_("{} (String)")), string_log->value); - } - } - else if (const geo_log::FieldInfoLog *gfield_value_log = - dynamic_cast(value_log)) - { - create_inspection_string_for_field_info(socket, *gfield_value_log, buf); - } - else if (const geo_log::GeometryInfoLog *geo_value_log = - dynamic_cast(value_log)) - { - create_inspection_string_for_geometry_info(*geo_value_log, buf); - } - else if (const geo_log::BundleValueLog *bundle_value_log = - dynamic_cast(value_log)) - { - create_inspection_string_for_bundle(*bundle_value_log, buf); - } - else if (const geo_log::ClosureValueLog *closure_value_log = - dynamic_cast(value_log)) - { - create_inspection_string_for_closure(*closure_value_log, buf); - } - - std::string str = fmt::to_string(buf); - if (str.empty()) { - return std::nullopt; - } - return str; -} - -static std::optional create_declaration_inspection_string(const bNodeSocket &socket) -{ - fmt::memory_buffer buf; - if (const nodes::decl::Geometry *socket_decl = dynamic_cast( - socket.runtime->declaration)) - { - create_inspection_string_for_geometry_socket(buf, socket_decl); - } - - std::string str = fmt::to_string(buf); - if (str.empty()) { - return std::nullopt; - } - return str; -} - -static Vector lines_of_text(std::string text) -{ - Vector result; - std::istringstream text_stream(text); - for (std::string line; std::getline(text_stream, line);) { - result.append(line); - } - return result; -} - -static std::optional create_multi_input_log_inspection_string( - const bNodeSocket &socket, TreeDrawContext &tree_draw_ctx) -{ - if (!socket.is_multi_input()) { - return std::nullopt; - } - - Vector, 8> numerated_info; - - const Span connected_links = socket.directly_linked_links(); - for (const int index : connected_links.index_range()) { - const bNodeLink *link = connected_links[index]; - const int connection_number = index + 1; - if (!link->is_used()) { - continue; - } - if (!(link->flag & NODE_LINK_VALID)) { - continue; - } - if (link->fromnode->is_dangling_reroute()) { - continue; - } - const bNodeSocket &connected_socket = *link->fromsock; - geo_log::GeoTreeLog *geo_tree_log = tree_draw_ctx.tree_logs.get_main_tree_log( - connected_socket); - const std::optional input_log = create_log_inspection_string(geo_tree_log, - connected_socket); - if (!input_log.has_value()) { - continue; - } - numerated_info.append({connection_number, std::move(*input_log)}); - } - - if (numerated_info.is_empty()) { - return std::nullopt; - } - - fmt::memory_buffer buf; - for (const std::pair &info : numerated_info) { - const Vector lines = lines_of_text(info.second); - fmt::format_to(fmt::appender(buf), "{}", info.first); - fmt::format_to(fmt::appender(buf), ". "); - fmt::format_to(fmt::appender(buf), "{}", lines.first()); - for (const std::string &line : lines.as_span().drop_front(1)) { - fmt::format_to(fmt::appender(buf), "\n {}", line); - } - if (&info != &numerated_info.last()) { - buf.append(StringRef(".\n")); - } - } - - const std::string str = fmt::to_string(buf); - if (str.empty()) { - return std::nullopt; - } - - return str; -} - -static std::optional create_default_value_inspection_string(const bNodeSocket &socket) -{ - fmt::memory_buffer buf; - create_inspection_string_for_default_socket_value(socket, buf); - - std::string str = fmt::to_string(buf); - if (str.empty()) { - return std::nullopt; - } - return str; -} - -static const bNodeSocket *target_for_reroute(const bNodeSocket &reroute_output) -{ - const bNodeSocket *output = &reroute_output; - Set visited_nodes; - visited_nodes.add(&reroute_output.owner_node()); - while (true) { - const Span linked_sockets = output->directly_linked_sockets(); - if (linked_sockets.size() != 1) { - return nullptr; - } - const bNode &target_node = linked_sockets[0]->owner_node(); - if (!visited_nodes.add(&target_node)) { - return nullptr; - } - if (!target_node.is_dangling_reroute()) { - return linked_sockets[0]; - } - output = target_node.output_sockets()[0]; - } -} - -static std::optional create_dangling_reroute_inspection_string( - const bNodeTree &ntree, const bNodeSocket &socket) -{ - if (ntree.type != NTREE_GEOMETRY) { - return std::nullopt; - } - - const bNode &node = socket.owner_node(); - if (!node.is_dangling_reroute()) { - return std::nullopt; - } - - const bNodeSocket &output_socket = *node.output_sockets()[0]; - const bNodeSocket *target_socket = target_for_reroute(output_socket); - - if (target_socket == nullptr) { - if (!output_socket.directly_linked_sockets().is_empty()) { - return TIP_("Dangling reroute is ignored by all targets"); - } - return std::nullopt; - } - - if (target_socket->is_multi_input()) { - return TIP_("Dangling reroute branch is ignored by multi input socket"); - } - - fmt::memory_buffer buf; - create_inspection_string_for_default_socket_value(*target_socket, buf); - std::string str = fmt::to_string(buf); - if (str.empty()) { - return TIP_("Dangling reroute is ignored"); - } - fmt::format_to(fmt::appender(buf), ".\n\n"); - fmt::format_to(fmt::appender(buf), - "{}", - TIP_("Dangling reroute is ignored, default value of target socket is used")); - return str; -} - -static std::string node_socket_get_tooltip(const SpaceNode *snode, - const bNodeTree &ntree, - const bNodeSocket &socket) -{ - TreeDrawContext tree_draw_ctx; - if (snode != nullptr) { - if (ntree.type == NTREE_GEOMETRY) { - tree_draw_ctx.tree_logs = geo_log::GeoNodesLog::get_contextual_tree_logs(*snode); - } - } - - geo_log::GeoTreeLog *geo_tree_log = tree_draw_ctx.tree_logs.get_main_tree_log(socket); - - Vector inspection_strings; - - if (std::optional info = create_description_inspection_string(socket)) { - inspection_strings.append(std::move(*info)); - } - if (std::optional info = create_log_inspection_string(geo_tree_log, socket)) { - inspection_strings.append(std::move(*info)); - } - else if (std::optional info = create_dangling_reroute_inspection_string(ntree, - socket)) - { - inspection_strings.append(std::move(*info)); - } - else if (std::optional info = create_default_value_inspection_string(socket)) { - inspection_strings.append(std::move(*info)); - } - else if (std::optional info = create_multi_input_log_inspection_string( - socket, tree_draw_ctx)) - { - inspection_strings.append(std::move(*info)); - } - if (std::optional info = create_declaration_inspection_string(socket)) { - inspection_strings.append(std::move(*info)); - } - if (U.experimental.use_socket_structure_type) { - if (socket.runtime->declaration) { - switch (socket.runtime->declaration->structure_type) { - case nodes::StructureType::Single: - inspection_strings.append(TIP_("(Single Value)")); - break; - case nodes::StructureType::Dynamic: - inspection_strings.append(TIP_("(Dynamic Structure Type)")); - break; - case nodes::StructureType::Field: - inspection_strings.append(TIP_("(Field)")); - break; - case nodes::StructureType::Grid: - inspection_strings.append(TIP_("(Volume Grid)")); - break; - } - } - } - - std::stringstream output; - for (const std::string &info : inspection_strings) { - output << info; - if (&info != &inspection_strings.last()) { - output << ".\n\n"; - } - } - - if (inspection_strings.is_empty()) { - const bool is_extend = StringRef(socket.idname) == "NodeSocketVirtual"; - const bNode &node = socket.owner_node(); - if (node.is_reroute()) { - output << bke::node_label(ntree, node); - } - else if (is_extend) { - output << TIP_("Connect a link to create a new socket"); - } - else { - const StringRefNull socket_label = bke::node_socket_label(socket); - const char *socket_translation_context = node_socket_get_translation_context(socket); - const char *translated_socket_label = CTX_TIP_(socket_translation_context, - socket_label.c_str()); - output << translated_socket_label; - } - - if (ntree.type == NTREE_GEOMETRY && !is_extend) { - output << ".\n\n"; - output << TIP_( - "Unknown socket value. Either the socket was not used or its value was not logged " - "during the last evaluation"); - } - } - - return output.str(); -} - static void node_socket_add_tooltip_in_node_editor(const bNodeSocket &sock, uiLayout &layout) { - uiLayoutSetTooltipFunc( + uiLayoutSetTooltipCustomFunc( &layout, - [](bContext *C, void *argN, const StringRef /*tip*/) { - const SpaceNode &snode = *CTX_wm_space_node(C); + [](bContext &C, uiTooltipData &tip, uiBut *but, void *argN) { + const SpaceNode &snode = *CTX_wm_space_node(&C); const bNodeTree &ntree = *snode.edittree; const int index_in_tree = POINTER_AS_INT(argN); ntree.ensure_topology_cache(); - return node_socket_get_tooltip(&snode, ntree, *ntree.all_sockets()[index_in_tree]); + const bNodeSocket &socket = *ntree.all_sockets()[index_in_tree]; + build_socket_tooltip(tip, C, but, ntree, socket); }, POINTER_FROM_INT(sock.index_in_tree()), nullptr, @@ -2253,12 +1527,11 @@ void node_socket_add_tooltip(const bNodeTree &ntree, const bNodeSocket &sock, ui data->ntree = &ntree; data->socket = &sock; - uiLayoutSetTooltipFunc( + uiLayoutSetTooltipCustomFunc( &layout, - [](bContext *C, void *argN, const StringRef /*tip*/) { + [](bContext &C, uiTooltipData &tip, uiBut *but, void *argN) { SocketTooltipData *data = static_cast(argN); - const SpaceNode *snode = CTX_wm_space_node(C); - return node_socket_get_tooltip(snode, *data->ntree, *data->socket); + build_socket_tooltip(tip, C, but, *data->ntree, *data->socket); }, data, MEM_dupallocN, @@ -2641,20 +1914,32 @@ static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block if (input_socket && !input_socket->is_logically_linked()) { PointerRNA socket_ptr = RNA_pointer_create_discrete( &ntree.id, &RNA_NodeSocket, input_socket); - uiDefButR(&block, - UI_BTYPE_CHECKBOX, - -1, - "", - offsetx, - int(*panel_runtime.header_center_y - NODE_DYS), - UI_UNIT_X, - NODE_DY, - &socket_ptr, - "default_value", - 0, - 0, - 0, - ""); + uiBut *panel_toggle_but = uiDefButR(&block, + UI_BTYPE_CHECKBOX, + -1, + "", + offsetx, + int(*panel_runtime.header_center_y - NODE_DYS), + UI_UNIT_X, + NODE_DY, + &socket_ptr, + "default_value", + 0, + 0, + 0, + ""); + UI_but_func_tooltip_custom_set( + panel_toggle_but, + [](bContext &C, uiTooltipData &tip, uiBut *but, void *argN) { + const SpaceNode &snode = *CTX_wm_space_node(&C); + const bNodeTree &ntree = *snode.edittree; + const int index_in_tree = POINTER_AS_INT(argN); + ntree.ensure_topology_cache(); + const bNodeSocket &socket = *ntree.all_sockets()[index_in_tree]; + build_socket_tooltip(tip, C, but, ntree, socket); + }, + POINTER_FROM_INT(input_socket->index_in_tree()), + nullptr); offsetx += UI_UNIT_X; } diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index b15fc198abb..534d66f7761 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -447,4 +447,12 @@ MenuType add_root_catalogs_menu_type(); void NODE_OT_sockets_sync(wmOperatorType *ot); +/* node_socket_tooltip.cc */ + +void build_socket_tooltip(uiTooltipData &tip_data, + bContext &C, + uiBut *but, + const bNodeTree &tree, + const bNodeSocket &socket); + } // namespace blender::ed::space_node diff --git a/source/blender/editors/space_node/node_socket_tooltip.cc b/source/blender/editors/space_node/node_socket_tooltip.cc new file mode 100644 index 00000000000..aff12aaf19f --- /dev/null +++ b/source/blender/editors/space_node/node_socket_tooltip.cc @@ -0,0 +1,881 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include + +#include "BKE_context.hh" +#include "BKE_idtype.hh" +#include "BKE_lib_id.hh" +#include "BKE_node_enum.hh" +#include "BKE_node_runtime.hh" +#include "BKE_type_conversions.hh" + +#include "BLI_math_euler.hh" +#include "BLI_string.h" + +#include "BLT_translation.hh" + +#include "DNA_collection_types.h" +#include "DNA_material_types.h" + +#include "NOD_geometry_nodes_log.hh" +#include "NOD_node_declaration.hh" +#include "NOD_socket.hh" + +#include "node_intern.hh" + +namespace geo_log = blender::nodes::geo_eval_log; + +namespace blender::ed::space_node { + +class SocketTooltipBuilder { + private: + uiTooltipData &tip_data_; + const bNodeTree &tree_; + const bNode &node_; + const bNodeSocket &socket_; + uiBut *but_ = nullptr; + bContext &C_; + int indentation_ = 0; + + enum class TooltipBlockType { + Label, + Description, + Value, + Python, + }; + + std::optional last_block_type_; + + public: + SocketTooltipBuilder(uiTooltipData &tip_data, + const bNodeTree &tree, + const bNodeSocket &socket, + bContext &C, + uiBut *but) + : tip_data_(tip_data), + tree_(tree), + node_(socket.owner_node()), + socket_(socket), + but_(but), + C_(C) + { + } + + void build() + { + const bool is_extend = StringRef(socket_.idname) == "NodeSocketVirtual"; + if (is_extend) { + this->build_tooltip_extend_socket(); + return; + } + if (node_.is_dangling_reroute()) { + this->build_tooltip_dangling_reroute(); + return; + } + if (this->should_show_label()) { + this->build_tooltip_label(); + } + this->build_tooltip_description(); + this->build_tooltip_value(); + this->build_python(); + + /* Extra padding at the bottom. */ + this->add_space(); + } + + private: + void build_tooltip_extend_socket() + { + this->add_text_field(TIP_("Connect a link to create a new socket.")); + } + + void build_tooltip_dangling_reroute() + { + this->add_text_field(TIP_("Dangling reroute nodes are ignored."), UI_TIP_LC_ALERT); + } + + bool should_show_label() + { + if (this->get_socket_description().value_or("").empty()) { + /* Show label when the description is empty so that the tooltip is never empty. */ + return true; + } + if (socket_.is_output()) { + return false; + } + if (socket_.type == SOCK_MENU) { + return true; + } + if (socket_.flag & SOCK_HIDE_LABEL) { + return true; + } + return false; + } + + void build_tooltip_label() + { + this->start_block(TooltipBlockType::Label); + if (node_.is_reroute()) { + this->add_text_field_header(TIP_("Reroute")); + return; + } + const StringRefNull translated_socket_label = node_socket_get_label(&socket_, nullptr); + this->add_text_field_header(translated_socket_label); + } + + void build_tooltip_description() + { + std::optional description_opt = this->get_socket_description(); + if (!description_opt) { + return; + } + std::string description = std::move(*description_opt); + if (description.empty()) { + return; + } + if (description[description.size() - 1] != '.') { + description += '.'; + } + this->start_block(TooltipBlockType::Description); + this->add_text_field(std::move(description)); + } + + std::optional get_socket_description() + { + if (socket_.runtime->declaration == nullptr) { + if (socket_.description[0]) { + return socket_.description; + } + return std::nullopt; + } + const nodes::SocketDeclaration &socket_decl = *socket_.runtime->declaration; + if (!socket_decl.description.empty()) { + return TIP_(socket_decl.description); + } + if (socket_decl.align_with_previous_socket) { + const Span all_items = node_.runtime->declaration->all_items; + for (const int i : all_items.index_range()) { + if (&*all_items[i] != &socket_decl) { + continue; + } + if (i == 0) { + break; + } + const nodes::SocketDeclaration *previous_socket_decl = + dynamic_cast(all_items[i - 1].get()); + if (!previous_socket_decl) { + break; + } + if (!previous_socket_decl->description.empty()) { + return TIP_(previous_socket_decl->description); + } + } + } + + return std::nullopt; + } + + void build_tooltip_value() + { + SpaceNode *snode = CTX_wm_space_node(&C_); + geo_log::ContextualGeoTreeLogs geo_tree_logs; + if (snode) { + geo_tree_logs = geo_log::GeoNodesLog::get_contextual_tree_logs(*snode); + } + geo_log::GeoTreeLog *geo_tree_log = geo_tree_logs.get_main_tree_log(socket_); + if (geo_tree_log && this->build_tooltip_value_from_geometry_nodes_log(*geo_tree_log)) { + return; + } + const bool always_show_value = socket_.owner_tree().type == NTREE_GEOMETRY; + if (node_.is_reroute()) { + if (always_show_value) { + this->start_block(TooltipBlockType::Value); + this->build_tooltip_value_unknown(); + } + return; + } + if (socket_.is_input()) { + if (this->is_socket_default_value_used()) { + this->build_tooltip_value_socket_default(); + return; + } + } + if (always_show_value) { + this->start_block(TooltipBlockType::Value); + this->build_tooltip_value_unknown(); + } + } + + void build_tooltip_value_unknown() + { + this->add_text_field_mono(TIP_("Value: Unknown (not evaluated)")); + } + + void build_tooltip_value_socket_default() + { + if (socket_.is_multi_input()) { + this->start_block(TooltipBlockType::Value); + this->add_text_field_mono(TIP_("Values: None")); + return; + } + const nodes::SocketDeclaration *socket_decl = socket_.runtime->declaration; + if (socket_decl && socket_decl->input_field_type == nodes::InputSocketFieldType::Implicit) { + this->start_block(TooltipBlockType::Value); + build_tooltip_value_implicit_default(socket_decl->default_input_type); + return; + } + if (socket_.typeinfo->base_cpp_type == nullptr) { + return; + } + const CPPType &cpp_type = *socket_.typeinfo->base_cpp_type; + BUFFER_FOR_CPP_TYPE_VALUE(cpp_type, socket_value); + socket_.typeinfo->get_base_cpp_value(socket_.default_value, socket_value); + BLI_SCOPED_DEFER([&]() { cpp_type.destruct(socket_value); }); + this->start_block(TooltipBlockType::Value); + this->build_tooltip_value_generic({cpp_type, socket_value}); + } + + [[nodiscard]] bool build_tooltip_value_from_geometry_nodes_log(geo_log::GeoTreeLog &geo_tree_log) + { + if (socket_.typeinfo->base_cpp_type == nullptr) { + return false; + } + geo_tree_log.ensure_socket_values(); + if (socket_.is_multi_input()) { + return this->build_tooltip_last_value_multi_input(geo_tree_log); + } + geo_log::ValueLog *value_log = geo_tree_log.find_socket_value_log(socket_); + if (!value_log) { + return false; + } + this->start_block(TooltipBlockType::Value); + this->build_tooltip_value_geo_log(*value_log); + return true; + } + + bool build_tooltip_last_value_multi_input(geo_log::GeoTreeLog &geo_tree_log) + { + const Span connected_links = socket_.directly_linked_links(); + + Vector> value_logs; + bool all_value_logs_missing = true; + for (const int i : connected_links.index_range()) { + const bNodeLink &link = *connected_links[i]; + if (!link.is_used()) { + continue; + } + if (!(link.flag & NODE_LINK_VALID)) { + continue; + } + const bNodeSocket &from_socket = *link.fromsock; + geo_log::ValueLog *value_log = geo_tree_log.find_socket_value_log(from_socket); + value_logs.append({i, value_log}); + if (value_log) { + all_value_logs_missing = false; + } + } + if (all_value_logs_missing) { + return false; + } + + this->start_block(TooltipBlockType::Value); + for (const auto &[i, value_log] : value_logs) { + const int connection_number = i + 1; + if (i > 0) { + this->add_space(); + } + this->add_text_field_mono(fmt::format("{}:", connection_number)); + this->add_space(); + indentation_++; + BLI_SCOPED_DEFER([&]() { indentation_--; }); + if (value_log) { + this->build_tooltip_value_geo_log(*value_log); + } + else { + this->build_tooltip_value_unknown(); + } + } + + return true; + } + + void build_tooltip_value_geo_log(geo_log::ValueLog &value_log) + { + if (const auto *generic_value_log = dynamic_cast(&value_log)) + { + this->build_tooltip_value_generic(generic_value_log->value); + } + else if (const auto *string_value_log = dynamic_cast(&value_log)) { + this->build_tooltip_value_string_log(*string_value_log); + } + else if (const auto *field_value_log = dynamic_cast(&value_log)) + { + this->build_tooltip_value_field_log(*field_value_log); + } + else if (const auto *geometry_log = dynamic_cast(&value_log)) + { + this->build_tooltip_value_geometry_log(*geometry_log); + } + else if (const auto *grid_log = dynamic_cast(&value_log)) { + build_tooltip_value_grid_log(*grid_log); + } + else if (const auto *bundle_log = dynamic_cast(&value_log)) { + this->build_tooltip_value_bundle_log(*bundle_log); + } + else if (const auto *closure_log = dynamic_cast(&value_log)) + { + this->build_tooltip_value_closure_log(*closure_log); + } + } + + void build_tooltip_value_and_type_oneline(const StringRef value, const StringRef type) + { + this->add_text_field_mono(fmt::format("{}: {}", TIP_("Value"), value)); + this->add_space(); + this->add_text_field_mono(fmt::format("{}: {}", TIP_("Type"), type)); + } + + template [[nodiscard]] bool build_tooltip_value_data_block(const GPointer &value) + { + const CPPType &type = *value.type(); + if (!type.is()) { + return false; + } + const T *data = *value.get(); + std::string value_str; + if (data) { + value_str = BKE_id_name(id_cast(*data)); + } + else { + value_str = TIP_("None"); + } + const ID_Type id_type = T::id_type; + const char *id_type_name = BKE_idtype_idcode_to_name(id_type); + + this->build_tooltip_value_and_type_oneline(value_str, TIP_(id_type_name)); + return true; + } + + void build_tooltip_value_enum(const int item_identifier) + { + const auto *storage = socket_.default_value_typed(); + if (!storage->enum_items || storage->has_conflict()) { + this->build_tooltip_value_and_type_oneline(TIP_("Unknown"), TIP_("Menu")); + return; + } + const bke::RuntimeNodeEnumItem *enum_item = storage->enum_items->find_item_by_identifier( + item_identifier); + if (!enum_item) { + return; + } + if (!enum_item->description.empty()) { + this->add_text_field(enum_item->description, UI_TIP_LC_VALUE); + this->add_space(); + } + this->build_tooltip_value_and_type_oneline(enum_item->name, TIP_("Menu")); + } + + void build_tooltip_value_int(const int value) + { + std::string value_str = fmt::format("{}", value); + this->build_tooltip_value_and_type_oneline(value_str, TIP_("Integer")); + } + + void build_tooltip_value_float(const float value) + { + std::string value_str; + /* Above that threshold floats can't represent fractions anymore. */ + if (std::abs(value) > (1 << 24)) { + /* Use higher precision to display correct integer value instead of one that is rounded to + * fewer significant digits. */ + value_str = fmt::format("{:.10}", value); + } + else { + value_str = fmt::format("{}", value); + } + this->build_tooltip_value_and_type_oneline(value_str, TIP_("Float")); + } + + void build_tooltip_value_float3(const float3 &value) + { + const std::string value_str = fmt::format("{} {} {}", value.x, value.y, value.z); + this->build_tooltip_value_and_type_oneline(value_str, TIP_("3D Float Vector")); + } + + void build_tooltip_value_color(const ColorGeometry4f &value) + { + const std::string value_str = fmt::format( + "{} {} {} {} ({})", value.r, value.g, value.b, value.a, TIP_("Linear")); + this->build_tooltip_value_and_type_oneline(value_str, TIP_("Float Color")); + this->add_space(); + + this->add_text_field_mono(TIP_("Display:"), UI_TIP_LC_NORMAL); + bool is_gamma = false; + const ColorManagedDisplay *display = nullptr; + if (but_) { + is_gamma = UI_but_is_color_gamma(*but_); + display = UI_but_cm_display_get(*but_); + } + UI_tooltip_color_field_add(tip_data_, float4(value), true, is_gamma, display, UI_TIP_LC_VALUE); + } + + void build_tooltip_value_quaternion(const math::Quaternion &value) + { + const math::EulerXYZ euler = math::to_euler(value); + const std::string value_str = fmt::format("{}" BLI_STR_UTF8_DEGREE_SIGN + " {}" BLI_STR_UTF8_DEGREE_SIGN + " {}" BLI_STR_UTF8_DEGREE_SIGN, + euler.x().degree(), + euler.y().degree(), + euler.z().degree()); + this->build_tooltip_value_and_type_oneline(value_str, TIP_("Rotation")); + } + + void build_tooltip_value_bool(const bool value) + { + std::string value_str = value ? TIP_("True") : TIP_("False"); + this->build_tooltip_value_and_type_oneline(value_str, TIP_("Boolean")); + } + + void build_tooltip_value_float4x4(const float4x4 &value) + { + /* Transpose to be able to print row by row. */ + const float4x4 value_transposed = math::transpose(value); + + std::stringstream ss; + for (const int row_i : IndexRange(4)) { + const float4 row = value_transposed[row_i]; + ss << fmt::format("{:7.3} {:7.3} {:7.3} {:7.3}\n", row[0], row[1], row[2], row[3]); + } + + this->add_text_field_mono(fmt::format("{}:", TIP_("Value"))); + this->add_space(); + this->add_text_field_mono(ss.str()); + this->add_space(); + this->add_text_field_mono(fmt::format("{}: {}", TIP_("Type"), TIP_("4x4 Float Matrix"))); + } + + void build_tooltip_value_generic(const GPointer &value) + + { + const CPPType &value_type = *value.type(); + if (this->build_tooltip_value_data_block(value)) { + return; + } + if (this->build_tooltip_value_data_block(value)) { + return; + } + if (this->build_tooltip_value_data_block(value)) { + return; + } + if (this->build_tooltip_value_data_block(value)) { + return; + } + if (this->build_tooltip_value_data_block(value)) { + return; + } + + if (socket_.type == SOCK_MENU) { + if (!value_type.is()) { + this->build_tooltip_value_unknown(); + return; + } + const int item_identifier = *value.get(); + this->build_tooltip_value_enum(item_identifier); + return; + } + + const CPPType &socket_base_cpp_type = *socket_.typeinfo->base_cpp_type; + const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); + if (value_type != socket_base_cpp_type) { + if (!conversions.is_convertible(value_type, socket_base_cpp_type)) { + this->build_tooltip_value_unknown(); + return; + } + } + BUFFER_FOR_CPP_TYPE_VALUE(socket_base_cpp_type, socket_value); + conversions.convert_to_uninitialized( + value_type, socket_base_cpp_type, value.get(), socket_value); + BLI_SCOPED_DEFER([&]() { socket_base_cpp_type.destruct(socket_value); }); + + if (socket_base_cpp_type.is()) { + this->build_tooltip_value_int(*static_cast(socket_value)); + return; + } + if (socket_base_cpp_type.is()) { + this->build_tooltip_value_float(*static_cast(socket_value)); + return; + } + if (socket_base_cpp_type.is()) { + this->build_tooltip_value_float3(*static_cast(socket_value)); + return; + } + if (socket_base_cpp_type.is()) { + this->build_tooltip_value_color(*static_cast(socket_value)); + return; + } + if (socket_base_cpp_type.is()) { + this->build_tooltip_value_quaternion(*static_cast(socket_value)); + return; + } + if (socket_base_cpp_type.is()) { + this->build_tooltip_value_bool(*static_cast(socket_value)); + return; + } + if (socket_base_cpp_type.is()) { + this->build_tooltip_value_float4x4(*static_cast(socket_value)); + return; + } + this->build_tooltip_value_unknown(); + } + + void build_tooltip_value_string_log(const geo_log::StringLog &value_log) + { + std::string value_str = value_log.value; + if (value_log.truncated) { + value_str += "..."; + } + this->build_tooltip_value_and_type_oneline(value_str, TIP_("String")); + } + + const char *get_field_type_name(const CPPType &base_type) + { + if (base_type.is()) { + return TIP_("Integer Field"); + } + if (base_type.is()) { + return TIP_("Float Field"); + } + if (base_type.is()) { + return TIP_("3D Float Vector Field"); + } + if (base_type.is()) { + return TIP_("Boolean Field"); + } + if (base_type.is()) { + return TIP_("String Field"); + } + if (base_type.is()) { + return TIP_("Color Field"); + } + if (base_type.is()) { + return TIP_("Rotation Field"); + } + BLI_assert_unreachable(); + return TIP_("Field"); + } + + void build_tooltip_value_field_log(const geo_log::FieldInfoLog &value_log) + { + const CPPType &socket_base_cpp_type = *socket_.typeinfo->base_cpp_type; + const Span input_tooltips = value_log.input_tooltips; + + if (input_tooltips.is_empty()) { + /* Should have been logged as constant value. */ + BLI_assert_unreachable(); + return; + } + + this->add_text_field_mono(TIP_("Field dependending on:")); + + for (const std::string &input_tooltip : input_tooltips) { + this->add_space(); + this->add_text_field_mono(fmt::format(" \u2022 {}", input_tooltip)); + } + + this->add_space(); + std::string type_str = this->get_field_type_name(socket_base_cpp_type); + this->add_text_field_mono(fmt::format("{}: {}", TIP_("Type"), type_str)); + } + + std::string count_to_string(const int count) + { + char str[BLI_STR_FORMAT_INT32_GROUPED_SIZE]; + BLI_str_format_int_grouped(str, count); + return std::string(str); + } + + void build_tooltip_value_geometry_log(const geo_log::GeometryInfoLog &geometry_log) + { + Span component_types = geometry_log.component_types; + if (component_types.is_empty()) { + this->build_tooltip_value_and_type_oneline(TIP_("None"), TIP_("Geometry Set")); + return; + } + this->add_text_field_mono(TIP_("Geometry components:")); + for (const bke::GeometryComponent::Type type : component_types) { + std::string component_str; + switch (type) { + case bke::GeometryComponent::Type::Mesh: { + const geo_log::GeometryInfoLog::MeshInfo &info = *geometry_log.mesh_info; + component_str = fmt::format(fmt::runtime(TIP_("Mesh: {} vertices, {} edges, {} faces")), + this->count_to_string(info.verts_num), + this->count_to_string(info.edges_num), + this->count_to_string(info.faces_num)); + break; + } + case bke::GeometryComponent::Type::PointCloud: { + const geo_log::GeometryInfoLog::PointCloudInfo &info = *geometry_log.pointcloud_info; + component_str = fmt::format(fmt::runtime(TIP_("Point Cloud: {} points")), + this->count_to_string(info.points_num)); + break; + } + case bke::GeometryComponent::Type::Instance: { + const geo_log::GeometryInfoLog::InstancesInfo &info = *geometry_log.instances_info; + component_str = fmt::format(fmt::runtime(TIP_("Instances: {}")), + this->count_to_string(info.instances_num)); + break; + } + case bke::GeometryComponent::Type::Volume: { + const geo_log::GeometryInfoLog::VolumeInfo &info = *geometry_log.volume_info; + component_str = fmt::format(fmt::runtime(TIP_("Volume: {} grids")), + this->count_to_string(info.grids_num)); + break; + } + case bke::GeometryComponent::Type::Curve: { + const geo_log::GeometryInfoLog::CurveInfo &info = *geometry_log.curve_info; + component_str = fmt::format(fmt::runtime(TIP_("Curve: {} points, {} splines")), + this->count_to_string(info.points_num), + this->count_to_string(info.splines_num)); + break; + } + case bke::GeometryComponent::Type::GreasePencil: { + const geo_log::GeometryInfoLog::GreasePencilInfo &info = + *geometry_log.grease_pencil_info; + component_str = fmt::format(fmt::runtime(TIP_("Grease Pencil: {} layers")), + this->count_to_string(info.layers_num)); + break; + } + case bke::GeometryComponent::Type::Edit: { + if (geometry_log.edit_data_info.has_value()) { + const geo_log::GeometryInfoLog::EditDataInfo &info = *geometry_log.edit_data_info; + component_str = fmt::format( + fmt::runtime(TIP_("Edit: {}, {}, {}")), + info.has_deformed_positions ? TIP_("positions") : TIP_("no positions"), + info.has_deform_matrices ? TIP_("matrices") : TIP_("no matrices"), + info.gizmo_transforms_num > 0 ? TIP_("gizmos") : TIP_("no gizmos")); + } + break; + } + } + if (!component_str.empty()) { + this->add_space(); + this->add_text_field_mono(fmt::format(" \u2022 {}", component_str)); + } + } + this->add_space(); + this->add_text_field_mono(TIP_("Type: Geometry Set")); + } + + void build_tooltip_value_grid_log(const geo_log::GridInfoLog &grid_log) + { + std::string value_str; + if (grid_log.is_empty) { + value_str = TIP_("None"); + } + else { + value_str = TIP_("Volume Grid"); + } + this->build_tooltip_value_and_type_oneline(value_str, TIP_("Volume Grid")); + } + + void build_tooltip_value_bundle_log(const geo_log::BundleValueLog &bundle_log) + { + if (bundle_log.items.is_empty()) { + this->add_text_field_mono(TIP_("Values: None")); + } + else { + this->add_text_field_mono(TIP_("Values:")); + Vector sorted_items = bundle_log.items; + std::sort(sorted_items.begin(), sorted_items.end(), [](const auto &a, const auto &b) { + return BLI_strcasecmp_natural(a.key.identifiers().first().c_str(), + b.key.identifiers().first().c_str()) < 0; + }); + for (const geo_log::BundleValueLog::Item &item : sorted_items) { + this->add_space(); + const std::string type_name = TIP_(item.type->label); + this->add_text_field_mono(fmt::format( + fmt::runtime("\u2022 \"{}\" ({})\n"), item.key.identifiers().first(), type_name)); + } + } + this->add_space(); + this->add_text_field_mono(TIP_("Type: Bundle")); + } + + void build_tooltip_value_closure_log(const geo_log::ClosureValueLog &closure_log) + { + if (closure_log.inputs.is_empty() && closure_log.outputs.is_empty()) { + this->add_text_field_mono(TIP_("Value: None")); + } + else { + if (!closure_log.inputs.is_empty()) { + this->add_text_field_mono(TIP_("Inputs:")); + for (const geo_log::ClosureValueLog::Item &item : closure_log.inputs) { + this->add_space(); + const std::string type_name = TIP_(item.type->label); + this->add_text_field_mono(fmt::format( + fmt::runtime("\u2022 \"{}\" ({})\n"), item.key.identifiers().first(), type_name)); + } + } + if (!closure_log.outputs.is_empty()) { + this->add_space(); + this->add_text_field_mono(TIP_("Outputs:")); + for (const geo_log::ClosureValueLog::Item &item : closure_log.outputs) { + this->add_space(); + const std::string type_name = TIP_(item.type->label); + this->add_text_field_mono(fmt::format( + fmt::runtime("\u2022 \"{}\" ({})\n"), item.key.identifiers().first(), type_name)); + } + } + } + this->add_space(); + this->add_text_field_mono(TIP_("Type: Closure")); + } + + void build_tooltip_value_implicit_default(const NodeDefaultInputType &type) + { + switch (type) { + case NODE_DEFAULT_INPUT_VALUE: { + /* Should be handled elsewhere. */ + BLI_assert_unreachable(); + break; + } + case NODE_DEFAULT_INPUT_INDEX_FIELD: { + this->build_tooltip_value_and_type_oneline(TIP_("Index Field"), + this->get_field_type_name(CPPType::get())); + break; + } + case NODE_DEFAULT_INPUT_ID_INDEX_FIELD: { + this->build_tooltip_value_and_type_oneline(TIP_("ID or Index Field"), + this->get_field_type_name(CPPType::get())); + break; + } + case NODE_DEFAULT_INPUT_NORMAL_FIELD: { + this->build_tooltip_value_and_type_oneline( + TIP_("Normal Field"), this->get_field_type_name(CPPType::get())); + break; + } + case NODE_DEFAULT_INPUT_POSITION_FIELD: { + this->build_tooltip_value_and_type_oneline( + TIP_("Position Field"), this->get_field_type_name(CPPType::get())); + break; + } + case NODE_DEFAULT_INPUT_INSTANCE_TRANSFORM_FIELD: { + this->build_tooltip_value_and_type_oneline( + TIP_("Instance Transform Field"), this->get_field_type_name(CPPType::get())); + break; + } + case NODE_DEFAULT_INPUT_HANDLE_LEFT_FIELD: { + this->build_tooltip_value_and_type_oneline( + TIP_("Left Handle Field"), this->get_field_type_name(CPPType::get())); + break; + } + case NODE_DEFAULT_INPUT_HANDLE_RIGHT_FIELD: + this->build_tooltip_value_and_type_oneline( + TIP_("Right Handle Field"), this->get_field_type_name(CPPType::get())); + break; + } + } + + bool is_socket_default_value_used() + { + BLI_assert(socket_.is_input()); + for (const bNodeLink *link : socket_.directly_linked_links()) { + if (!link->is_used()) { + continue; + } + const bNodeSocket &from_socket = *link->fromsock; + const bNode &from_node = from_socket.owner_node(); + if (from_node.is_dangling_reroute()) { + continue; + } + return false; + } + return true; + } + + StringRef get_structure_type_tooltip(const nodes::StructureType &structure_type) + { + switch (structure_type) { + case nodes::StructureType::Single: { + return TIP_("Single Value"); + } + case nodes::StructureType::Dynamic: { + return TIP_("Dynamic"); + } + case nodes::StructureType::Field: { + return TIP_("Field"); + } + case nodes::StructureType::Grid: { + return TIP_("Volume Grid"); + } + } + BLI_assert_unreachable(); + return "Unknown"; + } + + void build_python() + { + if (!(U.flag & USER_TOOLTIPS_PYTHON)) { + return; + } + if (!but_) { + return; + } + UI_tooltip_uibut_python_add(tip_data_, C_, *but_, nullptr); + } + + void start_block(const TooltipBlockType new_block_type) + { + if (last_block_type_.has_value()) { + this->add_space(2); + } + last_block_type_ = new_block_type; + } + + void add_text_field_header(std::string text) + { + UI_tooltip_text_field_add( + tip_data_, this->indent(text), {}, UI_TIP_STYLE_HEADER, UI_TIP_LC_MAIN); + } + + void add_text_field(std::string text, const uiTooltipColorID color_id = UI_TIP_LC_NORMAL) + { + UI_tooltip_text_field_add(tip_data_, this->indent(text), {}, UI_TIP_STYLE_NORMAL, color_id); + } + + void add_text_field_mono(std::string text, const uiTooltipColorID color_id = UI_TIP_LC_VALUE) + { + UI_tooltip_text_field_add(tip_data_, this->indent(text), {}, UI_TIP_STYLE_MONO, color_id); + } + + void add_space(const int amount = 1) + { + for ([[maybe_unused]] const int i : IndexRange(amount)) { + UI_tooltip_text_field_add(tip_data_, {}, {}, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL); + } + } + + std::string indent(std::string text) + { + if (indentation_ == 0) { + return text; + } + return fmt::format("{: <{}}{}", "", indentation_, text); + } +}; + +void build_socket_tooltip(uiTooltipData &tip_data, + bContext &C, + uiBut *but, + const bNodeTree &tree, + const bNodeSocket &socket) +{ + SocketTooltipBuilder builder(tip_data, tree, socket, C, but); + builder.build(); +} + +} // namespace blender::ed::space_node diff --git a/source/blender/nodes/NOD_geometry_nodes_log.hh b/source/blender/nodes/NOD_geometry_nodes_log.hh index 0009fc5eabd..0e5043b4519 100644 --- a/source/blender/nodes/NOD_geometry_nodes_log.hh +++ b/source/blender/nodes/NOD_geometry_nodes_log.hh @@ -168,9 +168,6 @@ class GeometryInfoLog : public ValueLog { struct VolumeInfo { int grids_num; }; - struct GridInfo { - bool is_empty; - }; std::optional mesh_info; std::optional curve_info; @@ -179,10 +176,15 @@ class GeometryInfoLog : public ValueLog { std::optional instances_info; std::optional edit_data_info; std::optional volume_info; - std::optional grid_info; GeometryInfoLog(const bke::GeometrySet &geometry_set); - GeometryInfoLog(const bke::GVolumeGrid &grid); +}; + +class GridInfoLog : public ValueLog { + public: + bool is_empty = false; + + GridInfoLog(const bke::GVolumeGrid &grid); }; class BundleValueLog : public ValueLog { diff --git a/source/blender/nodes/intern/geometry_nodes_log.cc b/source/blender/nodes/intern/geometry_nodes_log.cc index d156d47c3bf..89c509dcc3b 100644 --- a/source/blender/nodes/intern/geometry_nodes_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -208,9 +208,8 @@ struct GridIsEmptyOp { }; #endif /* WITH_OPENVDB */ -GeometryInfoLog::GeometryInfoLog(const bke::GVolumeGrid &grid) +GridInfoLog::GridInfoLog(const bke::GVolumeGrid &grid) { - GridInfo &info = this->grid_info.emplace(); #ifdef WITH_OPENVDB bke::VolumeTreeAccessToken token; const openvdb::GridBase &vdb_grid = grid->grid(token); @@ -218,14 +217,14 @@ GeometryInfoLog::GeometryInfoLog(const bke::GVolumeGrid &grid) GridIsEmptyOp is_empty_op{vdb_grid}; if (BKE_volume_grid_type_operation(grid_type, is_empty_op)) { - info.is_empty = is_empty_op.result; + this->is_empty = is_empty_op.result; } else { - info.is_empty = true; + this->is_empty = true; } #else UNUSED_VARS(grid); - info.is_empty = true; + this->is_empty = true; #endif } @@ -312,7 +311,7 @@ void GeoTreeLogger::log_value(const bNode &node, const bNodeSocket &socket, cons #ifdef WITH_OPENVDB else if (value_variant.is_volume_grid()) { const bke::GVolumeGrid grid = value_variant.extract(); - store_logged_value(this->allocator->construct(grid)); + store_logged_value(this->allocator->construct(grid)); } #endif else if (value_variant.valid_for_socket(SOCK_BUNDLE)) {