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
This commit is contained in:
Lukas Tönne
2024-01-16 13:37:57 +01:00
parent f16dbd0360
commit 53e34dba80
5 changed files with 154 additions and 25 deletions
+1 -25
View File
@@ -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):
@@ -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.
@@ -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
@@ -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<blender::nodes::ItemDeclarationPtr>::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<bNode *>(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<const SocketDeclaration *>(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<const PanelDeclaration *>(
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<bNodeTree *>(ptr->owner_id);
bNode &node = *static_cast<bNode *>(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);
}
}
}
/** \} */
@@ -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