diff --git a/scripts/startup/bl_ui/space_node.py b/scripts/startup/bl_ui/space_node.py index 8212c2b82e3..d4ee6f2dcb9 100644 --- a/scripts/startup/bl_ui/space_node.py +++ b/scripts/startup/bl_ui/space_node.py @@ -705,31 +705,7 @@ class NODE_PT_active_node_properties(Panel): def draw(self, context): layout = self.layout node = context.active_node - # set "node" context pointer for the panel layout - layout.context_pointer_set("node", node) - - if hasattr(node, "draw_buttons_ext"): - node.draw_buttons_ext(context, layout) - elif hasattr(node, "draw_buttons"): - node.draw_buttons(context, layout) - - # XXX this could be filtered further to exclude socket types - # which don't have meaningful input values (e.g. cycles shader) - value_inputs = [socket for socket in node.inputs if self.show_socket_input(socket)] - if value_inputs: - layout.separator() - layout.label(text="Inputs:") - for socket in value_inputs: - row = layout.row() - socket.draw( - context, - row, - node, - iface_(socket.label if socket.label else socket.name, socket.bl_rna.translation_context), - ) - - def show_socket_input(self, socket): - return hasattr(socket, "draw") and socket.enabled and not socket.is_linked + layout.template_node_inputs(node) class NODE_PT_texture_mapping(Panel): diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index 81109a676fc..e2027c3058d 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -2712,6 +2712,10 @@ void uiTemplateGreasePencilLayerTree(uiLayout *layout, bContext *C); #endif void uiTemplateNodeTreeInterface(uiLayout *layout, PointerRNA *ptr); +/** + * Draw all node buttons and socket default values with the same panel structure used by the node. + */ +void uiTemplateNodeInputs(uiLayout *layout, bContext *C, PointerRNA *ptr); /** * \return: A RNA pointer for the operator properties. diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 5a1f98f5aa4..d31d67ee8c2 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -70,6 +70,7 @@ set(SRC interface_template_bone_collection_tree.cc interface_template_light_linking.cc interface_template_list.cc + interface_template_node_inputs.cc interface_template_node_tree_interface.cc interface_template_search_menu.cc interface_template_search_operator.cc diff --git a/source/blender/editors/interface/interface_template_node_inputs.cc b/source/blender/editors/interface/interface_template_node_inputs.cc new file mode 100644 index 00000000000..5b4c90f1417 --- /dev/null +++ b/source/blender/editors/interface/interface_template_node_inputs.cc @@ -0,0 +1,142 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include "BLI_vector.hh" + +#include "BKE_context.hh" +#include "BKE_node.hh" +#include "BKE_node_runtime.hh" +#include "BKE_screen.hh" + +#include "BLT_translation.h" + +#include "NOD_node_declaration.hh" + +#include "RNA_access.hh" +#include "RNA_prototypes.h" + +#include "UI_interface.hh" +#include "UI_resources.hh" + +/* -------------------------------------------------------------------- */ +/** \name Node Input Buttons Template + * \{ */ + +using blender::nodes::ItemDeclaration; +using blender::nodes::NodeDeclaration; +using blender::nodes::PanelDeclaration; +using blender::nodes::SocketDeclaration; + +using ItemIterator = blender::Vector::const_iterator; + +namespace blender::ui::nodes { + +static void draw_node_input(bContext *C, + uiLayout *layout, + PointerRNA *node_ptr, + bNodeSocket &socket) +{ + BLI_assert(socket.typeinfo != nullptr); + /* Ignore disabled sockets and linked sockets and sockets without a `draw` callback. */ + if (!socket.is_available() || (socket.flag & SOCK_IS_LINKED) || socket.typeinfo->draw == nullptr) + { + return; + } + + PointerRNA socket_ptr = RNA_pointer_create(node_ptr->owner_id, &RNA_NodeSocket, &socket); + const char *text = IFACE_(bke::nodeSocketLabel(&socket)); + socket.typeinfo->draw(C, layout, &socket_ptr, node_ptr, text); +} + +static void draw_node_input(bContext *C, + uiLayout *layout, + PointerRNA *node_ptr, + StringRefNull identifier) +{ + bNode &node = *static_cast(node_ptr->data); + bNodeSocket *socket = node.runtime->inputs_by_identifier.lookup(identifier); + draw_node_input(C, layout, node_ptr, *socket); +} + +/* Consume the item range, draw buttons if layout is not null. */ +static void handle_node_declaration_items(bContext *C, + Panel *root_panel, + uiLayout *layout, + PointerRNA *node_ptr, + ItemIterator &item_iter, + const ItemIterator item_end) +{ + while (item_iter != item_end) { + const ItemDeclaration *item_decl = item_iter->get(); + ++item_iter; + + if (const SocketDeclaration *socket_decl = dynamic_cast(item_decl)) + { + if (layout && socket_decl->in_out == SOCK_IN) { + draw_node_input(C, layout, node_ptr, socket_decl->identifier); + } + } + else if (const PanelDeclaration *panel_decl = dynamic_cast( + item_decl)) + { + const ItemIterator panel_item_end = item_iter + panel_decl->num_child_decls; + BLI_assert(panel_item_end <= item_end); + + /* Use a root panel property to toggle open/closed state. */ + const std::string panel_idname = "NodePanel" + std::to_string(panel_decl->identifier); + LayoutPanelState *state = BKE_panel_layout_panel_state_ensure( + root_panel, panel_idname.c_str(), panel_decl->default_collapsed); + PointerRNA state_ptr = RNA_pointer_create(nullptr, &RNA_LayoutPanelState, state); + uiLayout *panel_layout = uiLayoutPanel( + C, layout, IFACE_(panel_decl->name.c_str()), &state_ptr, "is_open"); + /* Draw panel buttons at the top of each panel section. */ + if (panel_layout && panel_decl->draw_buttons) { + panel_decl->draw_buttons(panel_layout, C, node_ptr); + } + + handle_node_declaration_items( + C, root_panel, panel_layout, node_ptr, item_iter, panel_item_end); + } + } +} + +} // namespace blender::ui::nodes + +void uiTemplateNodeInputs(uiLayout *layout, bContext *C, PointerRNA *ptr) +{ + bNodeTree &tree = *reinterpret_cast(ptr->owner_id); + bNode &node = *static_cast(ptr->data); + + tree.ensure_topology_cache(); + + BLI_assert(node.typeinfo != nullptr); + /* Draw top-level node buttons. */ + if (node.typeinfo->draw_buttons_ex) { + node.typeinfo->draw_buttons_ex(layout, C, ptr); + } + else if (node.typeinfo->draw_buttons) { + node.typeinfo->draw_buttons(layout, C, ptr); + } + + if (node.declaration()) { + /* Draw socket inputs and panel buttons in the order of declaration panels. */ + ItemIterator item_iter = node.declaration()->items.begin(); + const ItemIterator item_end = node.declaration()->items.end(); + Panel *root_panel = uiLayoutGetRootPanel(layout); + blender::ui::nodes::handle_node_declaration_items( + C, root_panel, layout, ptr, item_iter, item_end); + } + else { + /* Draw socket values using the flat inputs list. */ + for (bNodeSocket *input : node.runtime->inputs) { + blender::ui::nodes::draw_node_input(C, layout, ptr, *input); + } + } +} + +/** \} */ diff --git a/source/blender/makesrna/intern/rna_ui_api.cc b/source/blender/makesrna/intern/rna_ui_api.cc index 2f775dc3ea4..786a4820e95 100644 --- a/source/blender/makesrna/intern/rna_ui_api.cc +++ b/source/blender/makesrna/intern/rna_ui_api.cc @@ -2205,6 +2205,12 @@ void RNA_api_ui_layout(StructRNA *srna) "Node Tree Interface", "Interface of a node tree to display"); RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); + + func = RNA_def_function(srna, "template_node_inputs", "uiTemplateNodeInputs"); + RNA_def_function_ui_description(func, "Show a node settings and input socket values"); + RNA_def_function_flag(func, FUNC_USE_CONTEXT); + parm = RNA_def_pointer(func, "node", "Node", "Node", "Display inputs of this node"); + RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR); } #endif