From 53e34dba80554a9b5849b1fb33d8006ded21b541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20T=C3=B6nne?= Date: Tue, 16 Jan 2024 13:37:57 +0100 Subject: [PATCH] Fix #115727: Draw panel buttons in the node editor side bar In 13fac109 the node panels got support for individual option button callbacks, but these were not included in the node editor side bar. Only the older top-level buttons are drawn there. The panel structure is currently not accessible in python since it is part of the `NodeDeclaration` system. To draw node input sockets and buttons in the correct panel order as they appear on the node, a new template function `uiTemplateNodeInputs` has been added. This iterates over declared panels and their contents in the appropriate order and draws the buttons before sockets in the same panel. Pull Request: https://projects.blender.org/blender/blender/pulls/116936 --- scripts/startup/bl_ui/space_node.py | 26 +--- .../blender/editors/include/UI_interface_c.hh | 4 + .../blender/editors/interface/CMakeLists.txt | 1 + .../interface_template_node_inputs.cc | 142 ++++++++++++++++++ source/blender/makesrna/intern/rna_ui_api.cc | 6 + 5 files changed, 154 insertions(+), 25 deletions(-) create mode 100644 source/blender/editors/interface/interface_template_node_inputs.cc 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 8fb9090603c..9e2abe78625 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -2714,6 +2714,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