Refactor: Nodes: improve drawing of nodes based on node declaration

The main goal is to simplify adding support for nested node panels. The patch
makes use of the updated recursive node declarations introduced in
6ffc585fb8.

The main changes are:
* Rewritten node drawing in a way that makes ui design decisions like panel
  visibility and margins more explicit. Especially the handling of margins is
  much better now imo. Previously, it was very hard to change the margin for
  specific cases without accidentally breaking other situations. Now each
  possible case has an explicit margin. This needs a few more lines of code but
  is much easier to work with.
* Rewritten node drawing in panel (sidebar + material properties) using the new
  ways to iterate over the declaration.
* It's possible to add custom layouts at any point in the node declaration now.
  This also replaces the need for having a `draw_buttons` callback for panels.

Pull Request: https://projects.blender.org/blender/blender/pulls/128822
This commit is contained in:
Jacques Lucke
2024-10-11 12:20:58 +02:00
parent bce1ba16ae
commit a239bfc4dd
14 changed files with 840 additions and 614 deletions

View File

@@ -262,16 +262,19 @@ class bNodeSocketRuntime : NonCopyable, NonMovable {
int index_in_inout_sockets = -1;
};
struct bNodePanelExtent {
float min_y;
float max_y;
bool fill_node_end = false;
};
class bNodePanelRuntime : NonCopyable, NonMovable {
public:
/* The vertical location of the panel in the tree, calculated while drawing the nodes and invalid
* if the node tree hasn't been drawn yet. In the node tree's "world space" (the same as
* #bNode::runtime::totr). */
float location_y;
/* Vertical start location of the panel content. */
float min_content_y;
/* Vertical end location of the panel content. */
float max_content_y;
std::optional<float> header_center_y;
std::optional<bNodePanelExtent> content_extent;
};
/**

View File

@@ -12,6 +12,7 @@
#include "BLI_task.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
#include "NOD_node_declaration.hh"
namespace blender::bke::node_tree_runtime {
@@ -651,3 +652,13 @@ const bNode *bNodeTree::find_nested_node(const int32_t nested_node_id,
}
return group->find_nested_node(ref->path.id_in_node, r_tree);
}
const bNodeSocket &bNode::socket_by_decl(const blender::nodes::SocketDeclaration &decl) const
{
return decl.in_out == SOCK_IN ? this->input_socket(decl.index) : this->output_socket(decl.index);
}
bNodeSocket &bNode::socket_by_decl(const blender::nodes::SocketDeclaration &decl)
{
return decl.in_out == SOCK_IN ? this->input_socket(decl.index) : this->output_socket(decl.index);
}

View File

@@ -28,6 +28,7 @@
* \{ */
using blender::nodes::ItemDeclaration;
using blender::nodes::LayoutDeclaration;
using blender::nodes::NodeDeclaration;
using blender::nodes::PanelDeclaration;
using blender::nodes::SocketDeclaration;
@@ -66,54 +67,37 @@ static void draw_node_input(bContext *C,
socket.typeinfo->draw(C, row, &socket_ptr, node_ptr, text);
}
static void draw_node_input(bContext *C,
uiLayout *layout,
PointerRNA *node_ptr,
StringRefNull identifier)
static void draw_node_inputs_recursive(bContext *C,
uiLayout *layout,
bNode &node,
PointerRNA *node_ptr,
const blender::nodes::PanelDeclaration &panel_decl)
{
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);
/* Use a root panel property to toggle open/closed state. */
/* TODO: Use flag on the panel state instead which is better for dynamic panel amounts. */
const std::string panel_idname = "NodePanel" + std::to_string(panel_decl.identifier);
Panel *root_panel = uiLayoutGetRootPanel(layout);
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 = uiLayoutPanelProp(
C, layout, &state_ptr, "is_open", IFACE_(panel_decl.name.c_str()));
if (!(state->flag & LAYOUT_PANEL_STATE_FLAG_OPEN)) {
return;
}
for (const ItemDeclaration *item_decl : panel_decl.items) {
if (const auto *socket_decl = dynamic_cast<const SocketDeclaration *>(item_decl)) {
if (socket_decl->in_out == SOCK_IN) {
draw_node_input(C, panel_layout, node_ptr, node.socket_by_decl(*socket_decl));
}
}
else if (const PanelDeclaration *panel_decl = dynamic_cast<const PanelDeclaration *>(
item_decl))
{
const ItemIterator panel_item_end = item_iter + panel_decl->items.size();
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 = uiLayoutPanelProp(
C, layout, &state_ptr, "is_open", IFACE_(panel_decl->name.c_str()));
/* 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);
else if (const auto *sub_panel_decl = dynamic_cast<const PanelDeclaration *>(item_decl)) {
draw_node_inputs_recursive(C, panel_layout, node, node_ptr, *sub_panel_decl);
}
else if (const auto *layout_decl = dynamic_cast<const LayoutDeclaration *>(item_decl)) {
if (!layout_decl->is_default) {
layout_decl->draw(panel_layout, C, node_ptr);
}
handle_node_declaration_items(
C, root_panel, panel_layout, node_ptr, item_iter, panel_item_end);
}
}
}
@@ -122,6 +106,7 @@ static void handle_node_declaration_items(bContext *C,
void uiTemplateNodeInputs(uiLayout *layout, bContext *C, PointerRNA *ptr)
{
using namespace blender::nodes;
bNodeTree &tree = *reinterpret_cast<bNodeTree *>(ptr->owner_id);
bNode &node = *static_cast<bNode *>(ptr->data);
@@ -138,11 +123,22 @@ void uiTemplateNodeInputs(uiLayout *layout, bContext *C, PointerRNA *ptr)
if (node.declaration()) {
/* Draw socket inputs and panel buttons in the order of declaration panels. */
ItemIterator item_iter = node.declaration()->all_items.begin();
const ItemIterator item_end = node.declaration()->all_items.end();
Panel *root_panel = uiLayoutGetRootPanel(layout);
blender::ui::nodes::handle_node_declaration_items(
C, root_panel, layout, ptr, item_iter, item_end);
const NodeDeclaration &node_decl = *node.declaration();
for (const ItemDeclaration *item_decl : node_decl.root_items) {
if (const auto *panel_decl = dynamic_cast<const PanelDeclaration *>(item_decl)) {
blender::ui::nodes::draw_node_inputs_recursive(C, layout, node, ptr, *panel_decl);
}
else if (const auto *socket_decl = dynamic_cast<const SocketDeclaration *>(item_decl)) {
if (socket_decl->in_out == SOCK_IN) {
blender::ui::nodes::draw_node_input(C, layout, ptr, node.socket_by_decl(*socket_decl));
}
}
else if (const auto *layout_decl = dynamic_cast<const LayoutDeclaration *>(item_decl)) {
if (!layout_decl->is_default) {
layout_decl->draw(layout, C, ptr);
}
}
}
}
else {
/* Draw socket values using the flat inputs list. */

View File

@@ -25,6 +25,7 @@
#include "BLI_array.hh"
#include "BLI_bounds.hh"
#include "BLI_convexhull_2d.h"
#include "BLI_function_ref.hh"
#include "BLI_map.hh"
#include "BLI_set.hh"
#include "BLI_span.hh"
@@ -383,7 +384,7 @@ static bool is_node_panels_supported(const bNode &node)
static bool node_update_basis_buttons(const bContext &C,
bNodeTree &ntree,
bNode &node,
nodes::PanelDrawButtonsFunction draw_buttons,
blender::FunctionRef<nodes::DrawNodeLayoutFn> draw_buttons,
uiBlock &block,
int &dy)
{
@@ -550,378 +551,479 @@ static bool node_update_basis_socket(const bContext &C,
return true;
}
struct NodeInterfaceItemData {
private:
NodeInterfaceItemData() = default;
namespace flat_item {
public:
/* Declaration of a socket (only for socket items). */
const nodes::SocketDeclaration *socket_decl = nullptr;
enum class Type {
Socket,
Separator,
Layout,
PanelHeader,
PanelContentBegin,
PanelContentEnd,
};
struct Socket {
static constexpr Type type = Type::Socket;
bNodeSocket *input = nullptr;
bNodeSocket *output = nullptr;
/* Declaration of a panel (only for panel items). */
const nodes::PanelDeclaration *panel_decl = nullptr;
/* State of the panel instance on the node.
* Mutable so that panel visibility can be updated. */
bNodePanelState *state = nullptr;
/* Runtime panel state for draw locations. */
bke::bNodePanelRuntime *runtime = nullptr;
};
struct Separator {
static constexpr Type type = Type::Separator;
};
struct PanelHeader {
static constexpr Type type = Type::PanelHeader;
const nodes::PanelDeclaration *decl;
};
struct PanelContentBegin {
static constexpr Type type = Type::PanelContentBegin;
const nodes::PanelDeclaration *decl;
};
struct PanelContentEnd {
static constexpr Type type = Type::PanelContentEnd;
const nodes::PanelDeclaration *decl;
};
struct Layout {
static constexpr Type type = Type::Layout;
const nodes::LayoutDeclaration *decl;
};
bool is_separator = false;
} // namespace flat_item
NodeInterfaceItemData(const nodes::SocketDeclaration *_socket_decl,
bNodeSocket *_input,
bNodeSocket *_output)
: socket_decl(_socket_decl), input(_input), output(_output)
{
}
NodeInterfaceItemData(const nodes::PanelDeclaration *_panel_decl,
bNodePanelState *_state,
bke::bNodePanelRuntime *_runtime)
: panel_decl(_panel_decl), state(_state), runtime(_runtime)
{
}
struct FlatNodeItem {
std::variant<flat_item::Socket,
flat_item::Separator,
flat_item::PanelHeader,
flat_item::PanelContentBegin,
flat_item::PanelContentEnd,
flat_item::Layout>
item;
static NodeInterfaceItemData separator()
flat_item::Type type() const
{
NodeInterfaceItemData item;
item.is_separator = true;
return item;
}
bool is_valid_socket() const
{
/* At least one socket pointer must be valid. */
return this->socket_decl && (input || output);
}
bool is_valid_panel() const
{
/* Panel can only be drawn when state data is available. */
return this->panel_decl && this->state && this->runtime;
}
bool is_valid_separator() const
{
return this->is_separator;
return std::visit([](auto &&item) { return item.type; }, this->item);
}
};
/* Compile relevant socket and panel pointer data into a vector.
* This helps ensure correct pointer access in complex situations like inlined sockets.
*/
static Vector<NodeInterfaceItemData> node_build_item_data(bNode &node)
static void determine_potentially_visible_panels_recursive(
const bNode &node, const nodes::PanelDeclaration &panel_decl, MutableSpan<bool> r_result)
{
namespace nodes = blender::nodes;
using ItemDeclIterator = blender::Span<nodes::ItemDeclarationPtr>::iterator;
using SocketIterator = blender::Span<bNodeSocket *>::iterator;
using PanelStateIterator = blender::MutableSpan<bNodePanelState>::iterator;
using PanelRuntimeIterator = blender::MutableSpan<bke::bNodePanelRuntime>::iterator;
bool potentially_visible = false;
for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
const bNodeSocket &socket = node.socket_by_decl(*socket_decl);
potentially_visible |= socket.is_visible();
}
else if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl))
{
determine_potentially_visible_panels_recursive(node, *sub_panel_decl, r_result);
potentially_visible |= r_result[sub_panel_decl->index];
}
}
r_result[panel_decl.index] = potentially_visible;
}
/**
* A panel is potentially visible if it contains any socket that is available and not hidden.
*/
static void determine_potentially_visible_panels(const bNode &node, MutableSpan<bool> r_result)
{
for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) {
if (const auto *panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
determine_potentially_visible_panels_recursive(node, *panel_decl, r_result);
}
}
}
static void determine_visible_panels_impl_recursive(const bNode &node,
const nodes::PanelDeclaration &panel_decl,
const Span<bool> potentially_visible_states,
MutableSpan<bool> r_result)
{
if (!potentially_visible_states[panel_decl.index]) {
/* This panel does not contain any visible sockets.*/
return;
}
r_result[panel_decl.index] = true;
const bNodePanelState &panel_state = node.panel_states_array[panel_decl.index];
if (panel_state.is_collapsed()) {
/* The subpanels can't be visible if this panel is collapsed. */
return;
}
for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
determine_visible_panels_impl_recursive(
node, *sub_panel_decl, potentially_visible_states, r_result);
}
}
}
static void determine_visible_panels_impl(const bNode &node,
const Span<bool> potentially_visible_states,
MutableSpan<bool> r_result)
{
for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) {
if (const auto *panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
determine_visible_panels_impl_recursive(
node, *panel_decl, potentially_visible_states, r_result);
}
}
}
/**
* A panel is visible if all of the following are true:
* - All parent panels are visible and not collapsed.
* - The panel contains any visible sockets.
*/
static void determine_visible_panels(const bNode &node, MutableSpan<bool> r_visibility_states)
{
Array<bool> potentially_visible_states(r_visibility_states.size(), false);
determine_potentially_visible_panels(node, potentially_visible_states);
determine_visible_panels_impl(node, potentially_visible_states, r_visibility_states);
}
static void add_flat_items_for_socket(bNode &node,
const nodes::SocketDeclaration &socket_decl,
const nodes::PanelDeclaration *panel_decl,
Vector<FlatNodeItem> &r_items)
{
bNodeSocket &socket = node.socket_by_decl(socket_decl);
if (!socket_decl.align_with_previous_socket) {
r_items.append({flat_item::Socket()});
}
flat_item::Socket &item = std::get<flat_item::Socket>(r_items.last().item);
if (socket_decl.in_out == SOCK_IN) {
BLI_assert(!item.input);
item.input = &socket;
}
else {
BLI_assert(!item.output);
item.output = &socket;
}
item.panel_decl = panel_decl;
}
static void add_flat_items_for_separator(Vector<FlatNodeItem> &r_items)
{
r_items.append({flat_item::Separator()});
}
static void add_flat_items_for_layout(const bNode &node,
const nodes::LayoutDeclaration &layout_decl,
Vector<FlatNodeItem> &r_items)
{
if (!(node.flag & NODE_OPTIONS)) {
return;
}
r_items.append({flat_item::Layout{&layout_decl}});
}
static void add_flat_items_for_panel(bNode &node,
const nodes::PanelDeclaration &panel_decl,
const Span<bool> panel_visibility,
Vector<FlatNodeItem> &r_items)
{
if (!panel_visibility[panel_decl.index]) {
return;
}
r_items.append({flat_item::PanelHeader{&panel_decl}});
const bNodePanelState &panel_state = node.panel_states_array[panel_decl.index];
if (panel_state.is_collapsed()) {
return;
}
r_items.append({flat_item::PanelContentBegin{&panel_decl}});
for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
add_flat_items_for_socket(node, *socket_decl, &panel_decl, r_items);
}
else if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl))
{
add_flat_items_for_panel(node, *sub_panel_decl, panel_visibility, r_items);
}
else if (dynamic_cast<const nodes::SeparatorDeclaration *>(item_decl)) {
add_flat_items_for_separator(r_items);
}
else if (const auto *layout_decl = dynamic_cast<const nodes::LayoutDeclaration *>(item_decl)) {
add_flat_items_for_layout(node, *layout_decl, r_items);
}
}
r_items.append({flat_item::PanelContentEnd{&panel_decl}});
}
/**
* Flattens the visible panels, sockets etc. of the node into a list that is then used to draw it.
*/
static Vector<FlatNodeItem> make_flat_node_items(bNode &node)
{
BLI_assert(is_node_panels_supported(node));
BLI_assert(node.runtime->panels.size() == node.num_panel_states);
ItemDeclIterator item_decl = node.declaration()->all_items.begin();
SocketIterator input = node.input_sockets().begin();
SocketIterator output = node.output_sockets().begin();
PanelStateIterator panel_state = node.panel_states().begin();
PanelRuntimeIterator panel_runtime = node.runtime->panels.begin();
const ItemDeclIterator item_decl_end = node.declaration()->all_items.end();
const SocketIterator input_end = node.input_sockets().end();
const SocketIterator output_end = node.output_sockets().end();
const PanelStateIterator panel_state_end = node.panel_states().end();
const PanelRuntimeIterator panel_runtime_end = node.runtime->panels.end();
UNUSED_VARS_NDEBUG(input_end, output_end, panel_state_end, panel_runtime_end);
const int panels_num = node.num_panel_states;
Array<bool> panel_visibility(panels_num, false);
determine_visible_panels(node, panel_visibility);
Vector<NodeInterfaceItemData> result;
result.reserve(node.declaration()->all_items.size());
while (item_decl != item_decl_end) {
if (const nodes::SocketDeclaration *socket_decl =
dynamic_cast<const nodes::SocketDeclaration *>(item_decl->get()))
{
if (socket_decl->align_with_previous_socket) {
NodeInterfaceItemData &last_item = result.last();
switch (socket_decl->in_out) {
case SOCK_IN:
BLI_assert(input != input_end);
BLI_assert(last_item.input == nullptr);
last_item.input = *input;
++input;
break;
case SOCK_OUT:
BLI_assert(output != output_end);
BLI_assert(last_item.output == nullptr);
last_item.output = *output;
++output;
break;
}
}
else {
switch (socket_decl->in_out) {
case SOCK_IN:
BLI_assert(input != input_end);
result.append({socket_decl, *input, nullptr});
++input;
break;
case SOCK_OUT:
BLI_assert(output != output_end);
result.append({socket_decl, nullptr, *output});
++output;
break;
}
}
++item_decl;
Vector<FlatNodeItem> items;
for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) {
if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
add_flat_items_for_socket(node, *socket_decl, nullptr, items);
}
else if (const nodes::PanelDeclaration *panel_decl =
dynamic_cast<const nodes::PanelDeclaration *>(item_decl->get()))
{
BLI_assert(panel_state != panel_state_end);
BLI_assert(panel_runtime != panel_runtime_end);
result.append({panel_decl, panel_state, panel_runtime});
++item_decl;
++panel_state;
++panel_runtime;
else if (const auto *panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
add_flat_items_for_panel(node, *panel_decl, panel_visibility, items);
}
else if (dynamic_cast<const nodes::SeparatorDeclaration *>(item_decl->get())) {
result.append(NodeInterfaceItemData::separator());
++item_decl;
else if (dynamic_cast<const nodes::SeparatorDeclaration *>(item_decl)) {
add_flat_items_for_separator(items);
}
else if (const auto *layout_decl = dynamic_cast<const nodes::LayoutDeclaration *>(item_decl)) {
add_flat_items_for_layout(node, *layout_decl, items);
}
}
return result;
return items;
}
using ItemIterator = Vector<NodeInterfaceItemData>::const_iterator;
struct VisibilityUpdateState {
ItemIterator item_iter;
const ItemIterator item_end;
explicit VisibilityUpdateState(const Span<NodeInterfaceItemData> items)
: item_iter(items.begin()), item_end(items.end())
{
}
};
/* Recursive function to determine visibility of items before drawing. */
static void node_update_panel_items_visibility_recursive(int num_items,
const bool is_parent_collapsed,
bNodePanelState &parent_state,
VisibilityUpdateState &state)
/** Get the height of an empty node body. */
static float get_margin_empty()
{
parent_state.flag &= ~NODE_PANEL_CONTENT_VISIBLE;
while (state.item_iter != state.item_end) {
/* Stop after adding the expected number of items.
* Root panel consumes all remaining items (num_items == -1). */
if (num_items == 0) {
return NODE_DYS;
}
/** Get the margin between the node header and the first item. */
static float get_margin_from_top(const Span<FlatNodeItem> items)
{
const FlatNodeItem &first_item = items[0];
const flat_item::Type first_item_type = first_item.type();
switch (first_item_type) {
case flat_item::Type::Socket:
return 2 * NODE_ITEM_SPACING_Y;
case flat_item::Type::Separator:
return NODE_ITEM_SPACING_Y / 2;
case flat_item::Type::Layout:
return 3 * NODE_ITEM_SPACING_Y;
case flat_item::Type::PanelHeader:
return 4 * NODE_ITEM_SPACING_Y;
case flat_item::Type::PanelContentBegin:
case flat_item::Type::PanelContentEnd:
break;
}
BLI_assert_unreachable();
return 0;
}
/** Get the margin between the last item and the node bottom. */
static float get_margin_to_bottom(const Span<FlatNodeItem> items)
{
const FlatNodeItem &last_item = items.last();
const flat_item::Type last_item_type = last_item.type();
switch (last_item_type) {
case flat_item::Type::Socket:
return 5 * NODE_ITEM_SPACING_Y;
case flat_item::Type::Separator:
return NODE_ITEM_SPACING_Y;
case flat_item::Type::Layout:
return 5 * NODE_ITEM_SPACING_Y;
case flat_item::Type::PanelHeader:
return 4 * NODE_ITEM_SPACING_Y;
case flat_item::Type::PanelContentBegin:
break;
case flat_item::Type::PanelContentEnd:
return 3 * NODE_ITEM_SPACING_Y;
}
BLI_assert_unreachable();
return 0;
}
/** Get the margin between two consecutive items. */
static float get_margin_between_elements(const Span<FlatNodeItem> items, const int next_index)
{
BLI_assert(next_index >= 1);
const FlatNodeItem &prev = items[next_index - 1];
const FlatNodeItem &next = items[next_index];
using flat_item::Type;
const Type prev_type = prev.type();
const Type next_type = next.type();
/* Handle all cases explicitly. This simplifies modifying the margins for specific cases
* without breaking other cases significantly. */
switch (prev_type) {
case Type::Socket: {
switch (next_type) {
case Type::Socket:
return NODE_ITEM_SPACING_Y;
case Type::Separator:
return 0;
case Type::Layout:
return 2 * NODE_ITEM_SPACING_Y;
case Type::PanelHeader:
return 2 * NODE_ITEM_SPACING_Y;
case Type::PanelContentBegin:
break;
case Type::PanelContentEnd:
return 2 * NODE_ITEM_SPACING_Y;
}
break;
}
else if (num_items > 0) {
--num_items;
case Type::Layout: {
switch (next_type) {
case Type::Socket:
return 2 * NODE_ITEM_SPACING_Y;
case Type::Separator:
return 0;
case Type::Layout:
return NODE_ITEM_SPACING_Y;
case Type::PanelHeader:
return 3 * NODE_ITEM_SPACING_Y;
case Type::PanelContentBegin:
break;
case Type::PanelContentEnd:
return 2 * NODE_ITEM_SPACING_Y;
}
break;
}
/* Consume item. */
const NodeInterfaceItemData &item = *state.item_iter++;
case Type::Separator: {
switch (next_type) {
case Type::Socket:
return 2 * NODE_ITEM_SPACING_Y;
case Type::Separator:
return NODE_ITEM_SPACING_Y;
case Type::Layout:
return NODE_ITEM_SPACING_Y;
case Type::PanelHeader:
return NODE_ITEM_SPACING_Y;
case Type::PanelContentBegin:
break;
case Type::PanelContentEnd:
return NODE_ITEM_SPACING_Y;
}
break;
}
case Type::PanelHeader: {
switch (next_type) {
case Type::Socket:
return 4 * NODE_ITEM_SPACING_Y;
case Type::Separator:
return 3 * NODE_ITEM_SPACING_Y;
case Type::Layout:
return 3 * NODE_ITEM_SPACING_Y;
case Type::PanelHeader:
return 5 * NODE_ITEM_SPACING_Y;
case Type::PanelContentBegin:
return 3 * NODE_ITEM_SPACING_Y;
case Type::PanelContentEnd:
break;
}
break;
}
case Type::PanelContentBegin: {
switch (next_type) {
case Type::Socket:
return 2 * NODE_ITEM_SPACING_Y;
case Type::Separator:
return NODE_ITEM_SPACING_Y;
case Type::Layout:
return 2 * NODE_ITEM_SPACING_Y;
case Type::PanelHeader:
return 3 * NODE_ITEM_SPACING_Y;
case Type::PanelContentBegin:
break;
case Type::PanelContentEnd:
return NODE_ITEM_SPACING_Y;
}
break;
}
case Type::PanelContentEnd: {
switch (next_type) {
case Type::Socket:
return NODE_ITEM_SPACING_Y;
case Type::Separator:
return NODE_ITEM_SPACING_Y;
case Type::Layout:
return NODE_ITEM_SPACING_Y;
case Type::PanelHeader:
return 3 * NODE_ITEM_SPACING_Y;
case Type::PanelContentBegin:
break;
case Type::PanelContentEnd:
return NODE_ITEM_SPACING_Y;
}
break;
}
}
BLI_assert_unreachable();
return 0.0f;
}
if (item.is_valid_panel()) {
SET_FLAG_FROM_TEST(item.state->flag, is_parent_collapsed, NODE_PANEL_PARENT_COLLAPSED);
/* New top panel is collapsed if self or parent is collapsed. */
const bool is_collapsed = is_parent_collapsed || item.state->is_collapsed();
node_update_panel_items_visibility_recursive(
item.panel_decl->items.size(), is_collapsed, *item.state, state);
if (item.panel_decl->draw_buttons) {
item.state->flag |= NODE_PANEL_CONTENT_VISIBLE;
}
if (item.state->flag & NODE_PANEL_CONTENT_VISIBLE) {
/* If child panel is visible so is the parent panel. */
parent_state.flag |= NODE_PANEL_CONTENT_VISIBLE;
}
/** Tags all the sockets in the panel as collapsed and updates their positions. */
static void mark_sockets_collapsed_recursive(bNode &node,
const int node_left_x,
const nodes::PanelDeclaration &visible_panel_decl,
const nodes::PanelDeclaration &panel_decl)
{
const bke::bNodePanelRuntime &visible_panel_runtime =
node.runtime->panels[visible_panel_decl.index];
for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
bNodeSocket &socket = node.socket_by_decl(*socket_decl);
const int socket_x = socket.in_out == SOCK_IN ? node_left_x : node_left_x + NODE_WIDTH(node);
socket.runtime->location = math::round(
float2(socket_x, *visible_panel_runtime.header_center_y));
socket.flag |= SOCK_PANEL_COLLAPSED;
}
else if (item.is_valid_socket()) {
if (item.input) {
SET_FLAG_FROM_TEST(item.input->flag, is_parent_collapsed, SOCK_PANEL_COLLAPSED);
if (item.input->is_visible()) {
parent_state.flag |= NODE_PANEL_CONTENT_VISIBLE;
}
}
if (item.output) {
SET_FLAG_FROM_TEST(item.output->flag, is_parent_collapsed, SOCK_PANEL_COLLAPSED);
if (item.output->is_visible()) {
parent_state.flag |= NODE_PANEL_CONTENT_VISIBLE;
}
}
}
else if (item.is_valid_separator()) {
/* Nothing to do. */
}
else {
/* Should not happen. */
BLI_assert_unreachable();
else if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl))
{
mark_sockets_collapsed_recursive(node, node_left_x, visible_panel_decl, *sub_panel_decl);
}
}
}
struct LocationUpdateState {
ItemIterator item_iter;
const ItemIterator item_end;
/* Checked at various places to avoid adding duplicate spacers without anything in between. */
bool need_spacer_after_item = false;
/* Makes sure buttons are only drawn once. */
bool buttons_drawn = false;
/* Only true for the first item in the layout. */
bool is_first = true;
explicit LocationUpdateState(const Span<NodeInterfaceItemData> items)
: item_iter(items.begin()), item_end(items.end())
{
}
};
/* Recursive function that adds the expected number of items in a panel and advances the
* iterator. */
static void add_panel_items_recursive(const bContext &C,
bNodeTree &ntree,
bNode &node,
uiBlock &block,
const int locx,
int &locy,
int num_items,
const bool is_parent_collapsed,
const char *parent_label,
bke::bNodePanelRuntime *parent_runtime,
LocationUpdateState &state)
static void update_collapsed_sockets_recursive(bNode &node,
const int node_left_x,
const nodes::PanelDeclaration &panel_decl)
{
while (state.item_iter != state.item_end) {
/* Stop after adding the expected number of items.
* Root panel consumes all remaining items (num_items == -1). */
if (num_items == 0) {
break;
const bNodePanelState &panel_state = node.panel_states_array[panel_decl.index];
const bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[panel_decl.index];
const bool is_open = panel_runtime.header_center_y.has_value() && !panel_state.is_collapsed();
if (!is_open) {
mark_sockets_collapsed_recursive(node, node_left_x, panel_decl, panel_decl);
return;
}
for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
update_collapsed_sockets_recursive(node, node_left_x, *sub_panel_decl);
}
else if (num_items > 0) {
--num_items;
}
}
/**
* Finds all collapsed sockets and updates them based on the visible parent panel that contains
* them.
*/
static void update_collapsed_sockets(bNode &node, const int node_left_x)
{
for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) {
if (const auto *panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
update_collapsed_sockets_recursive(node, node_left_x, *panel_decl);
}
/* Consume item. */
const NodeInterfaceItemData &item = *state.item_iter++;
}
}
if (item.is_valid_panel()) {
/* Draw buttons before the first panel. */
if (!state.buttons_drawn) {
state.buttons_drawn = true;
state.need_spacer_after_item = node_update_basis_buttons(
C, ntree, node, node.typeinfo->draw_buttons, block, locy);
}
/* Panel visible if any content is visible. */
if (item.state->has_visible_content()) {
if (!is_parent_collapsed) {
locy -= NODE_DY;
state.is_first = false;
}
/* New top panel is collapsed if self or parent is collapsed. */
const bool is_collapsed = is_parent_collapsed || item.state->is_collapsed();
/* Round the socket location to stop it from jiggling. */
item.runtime->location_y = round(locy + NODE_DYS);
if (is_collapsed) {
item.runtime->max_content_y = item.runtime->min_content_y = round(locy);
}
else {
locy -= NODE_ITEM_SPACING_Y / 2; /* Space at bottom of panel header. */
item.runtime->max_content_y = item.runtime->min_content_y = round(locy);
locy -= NODE_ITEM_SPACING_Y; /* Space at top of panel contents. */
node_update_basis_buttons(C, ntree, node, item.panel_decl->draw_buttons, block, locy);
}
add_panel_items_recursive(C,
ntree,
node,
block,
locx,
locy,
item.panel_decl->items.size(),
is_collapsed,
item.panel_decl->name.c_str(),
item.runtime,
state);
}
}
else if (item.is_valid_socket()) {
bool need_socket_spacing = false;
if (item.input) {
/* Draw buttons before the first input. */
if (!state.buttons_drawn) {
state.buttons_drawn = true;
state.need_spacer_after_item = node_update_basis_buttons(
C, ntree, node, node.typeinfo->draw_buttons, block, locy);
}
if (is_parent_collapsed) {
item.input->runtime->location = float2(locx, round(locy + NODE_DYS));
}
else {
/* Space between items. */
if (!state.is_first && item.input->is_visible()) {
need_socket_spacing = true;
}
}
}
if (item.output) {
if (is_parent_collapsed) {
item.output->runtime->location = float2(round(locx + NODE_WIDTH(node)),
round(locy + NODE_DYS));
}
else {
/* Space between items. */
if (!state.is_first && item.output->is_visible()) {
need_socket_spacing = true;
}
}
}
if (need_socket_spacing) {
locy -= NODE_ITEM_SPACING_Y;
}
if (!is_parent_collapsed &&
node_update_basis_socket(
C, ntree, node, parent_label, item.input, item.output, block, locx, locy))
{
state.is_first = false;
state.need_spacer_after_item = true;
}
}
else if (item.is_valid_separator()) {
if (!is_parent_collapsed) {
uiLayout *layout = UI_block_layout(&block,
UI_LAYOUT_VERTICAL,
UI_LAYOUT_PANEL,
locx + NODE_DYS,
locy,
NODE_WIDTH(node) - NODE_DY,
NODE_DY,
0,
UI_style_get_dpi());
uiItemS_ex(layout, 1.0, LayoutSeparatorType::Line);
UI_block_layout_resolve(&block, nullptr, nullptr);
locy -= NODE_ITEM_SPACING_Y;
}
/**
* Tag the innermost panel that goes to the very end of the node. The background color of that
* panel is extended to fill the entire rest of the node.
*/
static void tag_final_panel(bNode &node, const Span<FlatNodeItem> items)
{
const flat_item::PanelContentEnd *final_panel = nullptr;
for (int item_i = items.size() - 1; item_i >= 0; item_i--) {
const FlatNodeItem &item = items[item_i];
if (const auto *panel_item = std::get_if<flat_item::PanelContentEnd>(&item.item)) {
final_panel = panel_item;
}
else {
/* Should not happen. */
BLI_assert_unreachable();
break;
}
}
/* Finalize the vertical extent of the content. */
if (!is_parent_collapsed) {
if (parent_runtime) {
locy -= 2 * NODE_ITEM_SPACING_Y; /* Space at bottom of panel contents. */
parent_runtime->min_content_y = round(locy);
}
locy -= NODE_ITEM_SPACING_Y / 2; /* Space at top of next panel header. */
if (final_panel) {
bke::bNodePanelRuntime &final_panel_runtime = node.runtime->panels[final_panel->decl->index];
final_panel_runtime.content_extent->fill_node_end = true;
}
}
@@ -929,45 +1031,118 @@ static void add_panel_items_recursive(const bContext &C,
static void node_update_basis_from_declaration(
const bContext &C, bNodeTree &ntree, bNode &node, uiBlock &block, const int locx, int &locy)
{
namespace nodes = blender::nodes;
BLI_assert(is_node_panels_supported(node));
BLI_assert(node.runtime->panels.size() == node.num_panel_states);
const Vector<NodeInterfaceItemData> item_data = node_build_item_data(node);
/* Update item visibility flags first. */
VisibilityUpdateState visibility_state(item_data);
/* Dummy state item to write into, unused. */
bNodePanelState root_panel_state;
node_update_panel_items_visibility_recursive(-1, false, root_panel_state, visibility_state);
/* Space at the top. */
locy -= NODE_DYS / 2;
/* Start by adding root panel items. */
LocationUpdateState location_state(item_data);
/* Draw buttons at the top when the node has a custom socket order. This could be customized in
* the future to support showing the buttons in any place. */
if (node.declaration()->allow_any_socket_order) {
location_state.buttons_drawn = true;
location_state.need_spacer_after_item = node_update_basis_buttons(
C, ntree, node, node.typeinfo->draw_buttons, block, locy);
/* Reset states. */
for (bke::bNodePanelRuntime &panel_runtime : node.runtime->panels) {
panel_runtime.header_center_y.reset();
panel_runtime.content_extent.reset();
}
for (bNodeSocket *socket : node.input_sockets()) {
socket->flag &= ~SOCK_PANEL_COLLAPSED;
}
for (bNodeSocket *socket : node.output_sockets()) {
socket->flag &= ~SOCK_PANEL_COLLAPSED;
}
add_panel_items_recursive(
C, ntree, node, block, locx, locy, -1, false, "", nullptr, location_state);
/* Draw buttons at the bottom if no inputs exist. */
if (!location_state.buttons_drawn) {
location_state.need_spacer_after_item = node_update_basis_buttons(
C, ntree, node, node.typeinfo->draw_buttons, block, locy);
/* Gather flattened list of items in the node.*/
const Vector<FlatNodeItem> flat_items = make_flat_node_items(node);
if (flat_items.is_empty()) {
const float margin = get_margin_empty();
locy -= margin;
return;
}
if (location_state.need_spacer_after_item) {
locy -= NODE_DYS / 2;
for (const int item_i : flat_items.index_range()) {
/* Apply margins. This should be the only place that applies margins between elements so that
* it is easy change later on.*/
if (item_i == 0) {
const float margin = get_margin_from_top(flat_items);
locy -= margin;
}
else {
const float margin = get_margin_between_elements(flat_items, item_i);
locy -= margin;
}
const FlatNodeItem &item_variant = flat_items[item_i];
std::visit(
[&](const auto &item) {
using ItemT = std::decay_t<decltype(item)>;
if constexpr (std::is_same_v<ItemT, flat_item::Socket>) {
bNodeSocket *input_socket = item.input;
bNodeSocket *output_socket = item.output;
const nodes::PanelDeclaration *panel_decl = item.panel_decl;
const char *parent_label = panel_decl ? panel_decl->name.c_str() : "";
node_update_basis_socket(
C, ntree, node, parent_label, input_socket, output_socket, block, locx, locy);
}
else if constexpr (std::is_same_v<ItemT, flat_item::Layout>) {
const nodes::LayoutDeclaration &decl = *item.decl;
/* Round the node origin because text contents are always pixel-aligned. */
const float2 loc = math::round(node_to_view(node, float2(0)));
uiLayout *layout = UI_block_layout(&block,
UI_LAYOUT_VERTICAL,
UI_LAYOUT_PANEL,
loc.x + NODE_DYS,
locy,
NODE_WIDTH(node) - NODE_DY,
0,
0,
UI_style_get_dpi());
if (node.flag & NODE_MUTED) {
uiLayoutSetActive(layout, false);
}
PointerRNA node_ptr = RNA_pointer_create(&ntree.id, &RNA_Node, &node);
uiLayoutSetContextPointer(layout, "node", &node_ptr);
decl.draw(layout, const_cast<bContext *>(&C), &node_ptr);
UI_block_align_end(&block);
int buty;
UI_block_layout_resolve(&block, nullptr, &buty);
locy = buty;
}
else if constexpr (std::is_same_v<ItemT, flat_item::Separator>) {
uiLayout *layout = UI_block_layout(&block,
UI_LAYOUT_VERTICAL,
UI_LAYOUT_PANEL,
locx + NODE_DYS,
locy,
NODE_WIDTH(node) - NODE_DY,
NODE_DY,
0,
UI_style_get_dpi());
uiItemS_ex(layout, 1.0, LayoutSeparatorType::Line);
UI_block_layout_resolve(&block, nullptr, nullptr);
}
else if constexpr (std::is_same_v<ItemT, flat_item::PanelHeader>) {
const nodes::PanelDeclaration &node_decl = *item.decl;
bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[node_decl.index];
const float panel_header_height = NODE_DYS;
locy -= panel_header_height / 2;
panel_runtime.header_center_y = locy;
locy -= panel_header_height / 2;
}
else if constexpr (std::is_same_v<ItemT, flat_item::PanelContentBegin>) {
const nodes::PanelDeclaration &node_decl = *item.decl;
bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[node_decl.index];
panel_runtime.content_extent.emplace();
panel_runtime.content_extent->max_y = locy;
}
else if constexpr (std::is_same_v<ItemT, flat_item::PanelContentEnd>) {
const nodes::PanelDeclaration &node_decl = *item.decl;
bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[node_decl.index];
panel_runtime.content_extent->min_y = locy;
}
},
item_variant.item);
}
const float bottom_margin = get_margin_to_bottom(flat_items);
locy -= bottom_margin;
update_collapsed_sockets(node, locx);
tag_final_panel(node, flat_items);
}
/* Conventional drawing in outputs/buttons/inputs order. */
@@ -2460,103 +2635,64 @@ static void node_panel_toggle_button_cb(bContext *C, void *panel_state_argv, voi
}
/* Draw panel backgrounds first, so other node elements can be rendered on top. */
static void node_draw_panels_background(const bNode &node, uiBlock &block)
static void node_draw_panels_background(const bNode &node)
{
namespace nodes = blender::nodes;
BLI_assert(is_node_panels_supported(node));
BLI_assert(node.runtime->panels.size() == node.panel_states().size());
const nodes::NodeDeclaration &decl = *node.declaration();
const rctf &rct = node.runtime->totr;
float color_panel[4];
UI_GetThemeColorShade4fv(TH_NODE, -15, color_panel);
float panel_color[4];
UI_GetThemeColorShade4fv(TH_NODE, -15, panel_color);
const rctf &totr = node.runtime->totr;
/* True if the last panel is open, draw bottom gap as background. */
bool is_last_panel_visible = false;
float last_panel_content_y = 0.0f;
const nodes::PanelDeclaration *final_panel_decl = nullptr;
int panel_i = 0;
for (const nodes::ItemDeclarationPtr &item_decl : decl.all_items) {
const nodes::PanelDeclaration *panel_decl = dynamic_cast<nodes::PanelDeclaration *>(
item_decl.get());
if (panel_decl == nullptr) {
/* Not a panel. */
const nodes::NodeDeclaration &node_decl = *node.declaration();
for (const int panel_i : node_decl.panels.index_range()) {
const nodes::PanelDeclaration &panel_decl = *node_decl.panels[panel_i];
const bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[panel_i];
if (!panel_runtime.content_extent.has_value()) {
continue;
}
const bNodePanelState &state = node.panel_states()[panel_i];
const bke::bNodePanelRuntime &runtime = node.runtime->panels[panel_i];
/* Don't draw hidden or collapsed panels. */
const bool is_background_visible = state.has_visible_content() &&
!(state.is_collapsed() || state.is_parent_collapsed());
is_last_panel_visible = is_background_visible;
last_panel_content_y = runtime.max_content_y;
if (!is_background_visible) {
++panel_i;
continue;
}
UI_block_emboss_set(&block, UI_EMBOSS_NONE);
/* Panel background. */
const rctf content_rect = {rct.xmin, rct.xmax, runtime.min_content_y, runtime.max_content_y};
const rctf content_rect = {totr.xmin,
totr.xmax,
panel_runtime.content_extent->min_y,
panel_runtime.content_extent->max_y};
UI_draw_roundbox_corner_set(UI_CNR_NONE);
UI_draw_roundbox_4fv(&content_rect, true, BASIS_RAD, color_panel);
UI_block_emboss_set(&block, UI_EMBOSS);
++panel_i;
UI_draw_roundbox_4fv(&content_rect, true, BASIS_RAD, panel_color);
if (panel_runtime.content_extent->fill_node_end) {
final_panel_decl = &panel_decl;
}
}
/* If last item is an open panel, extend the panel background to cover the bottom border. */
if (is_last_panel_visible) {
UI_block_emboss_set(&block, UI_EMBOSS_NONE);
const rctf content_rect = {rct.xmin, rct.xmax, rct.ymin, last_panel_content_y};
if (final_panel_decl) {
const bke::bNodePanelRuntime &final_panel_runtime =
node.runtime->panels[final_panel_decl->index];
const rctf content_rect = {
totr.xmin, totr.xmax, totr.ymin, final_panel_runtime.content_extent->min_y};
UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT);
UI_draw_roundbox_4fv(&content_rect, true, BASIS_RAD, color_panel);
UI_block_emboss_set(&block, UI_EMBOSS);
const int repeats = final_panel_decl->depth() + 1;
for ([[maybe_unused]] const int i : IndexRange(repeats)) {
UI_draw_roundbox_4fv(&content_rect, true, BASIS_RAD, panel_color);
}
}
}
static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block)
{
namespace nodes = blender::nodes;
BLI_assert(is_node_panels_supported(node));
BLI_assert(node.runtime->panels.size() == node.panel_states().size());
const rctf &totr = node.runtime->totr;
const nodes::NodeDeclaration &decl = *node.declaration();
const rctf &rct = node.runtime->totr;
int panel_i = 0;
for (const nodes::ItemDeclarationPtr &item_decl : decl.all_items) {
const nodes::PanelDeclaration *panel_decl = dynamic_cast<nodes::PanelDeclaration *>(
item_decl.get());
if (panel_decl == nullptr) {
/* Not a panel. */
const nodes::NodeDeclaration &node_decl = *node.declaration();
for (const int panel_i : node_decl.panels.index_range()) {
const nodes::PanelDeclaration &panel_decl = *node_decl.panels[panel_i];
const bke::bNodePanelRuntime &panel_runtime = node.runtime->panels[panel_i];
const bNodePanelState &panel_state = node.panel_states_array[panel_i];
if (!panel_runtime.header_center_y.has_value()) {
continue;
}
const bNodePanelState &state = node.panel_states()[panel_i];
/* Don't draw hidden panels. */
const bool is_header_visible = state.has_visible_content() && !state.is_parent_collapsed();
if (!is_header_visible) {
++panel_i;
continue;
}
const bke::bNodePanelRuntime &runtime = node.runtime->panels[panel_i];
const rctf rect = {
rct.xmin,
rct.xmax,
runtime.location_y - NODE_DYS,
runtime.location_y + NODE_DYS,
};
const rctf header_rect = {totr.xmin,
totr.xmax,
*panel_runtime.header_center_y - NODE_DYS,
*panel_runtime.header_center_y + NODE_DYS};
UI_block_emboss_set(&block, UI_EMBOSS_NONE);
/* Collapse/expand icon. */
@@ -2564,9 +2700,9 @@ static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block
uiDefIconBut(&block,
UI_BTYPE_BUT_TOGGLE,
0,
state.is_collapsed() ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT,
rct.xmin + (NODE_MARGIN_X / 3),
runtime.location_y - but_size / 2,
panel_state.is_collapsed() ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT,
totr.xmin + (NODE_MARGIN_X / 3),
*panel_runtime.header_center_y - but_size / 2,
but_size,
but_size,
nullptr,
@@ -2575,43 +2711,45 @@ static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block
"");
/* Panel label. */
uiBut *but = uiDefBut(&block,
UI_BTYPE_LABEL,
0,
IFACE_(panel_decl->name.c_str()),
int(rct.xmin + NODE_MARGIN_X + 0.4f),
int(runtime.location_y - NODE_DYS),
short(rct.xmax - rct.xmin - (30.0f * UI_SCALE_FAC)),
short(NODE_DY),
nullptr,
0,
0,
"");
uiBut *label_but = uiDefBut(&block,
UI_BTYPE_LABEL,
0,
IFACE_(panel_decl.name.c_str()),
int(totr.xmin + NODE_MARGIN_X + 0.4f),
int(*panel_runtime.header_center_y - NODE_DYS),
short(totr.xmax - totr.xmin - (30.0f * UI_SCALE_FAC)),
short(NODE_DY),
nullptr,
0,
0,
"");
if (node.flag & NODE_MUTED) {
UI_but_flag_enable(but, UI_BUT_INACTIVE);
UI_but_flag_enable(label_but, UI_BUT_INACTIVE);
}
/* Invisible button covering the entire header for collapsing/expanding. */
const int header_but_margin = NODE_MARGIN_X / 3;
but = uiDefIconBut(&block,
UI_BTYPE_BUT_TOGGLE,
0,
ICON_NONE,
rect.xmin + header_but_margin,
rect.ymin,
std::max(int(rect.xmax - rect.xmin - 2 * header_but_margin), 0),
rect.ymax - rect.ymin,
nullptr,
0.0f,
0.0f,
panel_decl->description.c_str());
UI_but_func_pushed_state_set(but, [&state](const uiBut &) { return state.is_collapsed(); });
UI_but_func_set(
but, node_panel_toggle_button_cb, const_cast<bNodePanelState *>(&state), &ntree);
uiBut *toggle_action_but = uiDefIconBut(
&block,
UI_BTYPE_BUT_TOGGLE,
0,
ICON_NONE,
header_rect.xmin + header_but_margin,
header_rect.ymin,
std::max(int(header_rect.xmax - header_rect.xmin - 2 * header_but_margin), 0),
header_rect.ymax - header_rect.ymin,
nullptr,
0.0f,
0.0f,
panel_decl.description.c_str());
UI_but_func_pushed_state_set(
toggle_action_but, [&panel_state](const uiBut &) { return panel_state.is_collapsed(); });
UI_but_func_set(toggle_action_but,
node_panel_toggle_button_cb,
const_cast<bNodePanelState *>(&panel_state),
&ntree);
UI_block_emboss_set(&block, UI_EMBOSS);
++panel_i;
}
}
@@ -3622,7 +3760,7 @@ static void node_draw_basis(const bContext &C,
UI_draw_roundbox_4fv(&rect, true, corner_radius, color);
if (is_node_panels_supported(node)) {
node_draw_panels_background(node, block);
node_draw_panels_background(node);
}
}

View File

@@ -756,11 +756,9 @@ static void node_panel_toggle_button_cb(bContext *C, void *panel_state_argv, voi
}
static void ui_node_draw_panel(uiLayout &layout,
bContext &C,
bNodeTree &ntree,
const nodes::PanelDeclaration &panel_decl,
bNodePanelState &panel_state,
PointerRNA nodeptr)
bNodePanelState &panel_state)
{
uiLayout *row = uiLayoutRow(&layout, true);
uiLayoutSetPropDecorate(row, false);
@@ -784,11 +782,40 @@ static void ui_node_draw_panel(uiLayout &layout,
UI_but_drawflag_enable(but, UI_BUT_TEXT_LEFT | UI_BUT_NO_TOOLTIP);
UI_but_func_set(but, node_panel_toggle_button_cb, &panel_state, &ntree);
UI_block_emboss_set(block, UI_EMBOSS);
}
/* Panel buttons. */
if (!panel_state.is_collapsed() && panel_decl.draw_buttons) {
uiLayoutSetPropSep(&layout, true);
panel_decl.draw_buttons(&layout, &C, &nodeptr);
static void ui_node_draw_recursive(uiLayout &layout,
bContext &C,
bNodeTree &ntree,
bNode &node,
const nodes::PanelDeclaration &panel_decl,
const int depth)
{
bNodePanelState &panel_state = node.panel_states_array[panel_decl.index];
ui_node_draw_panel(layout, ntree, panel_decl, panel_state);
if (panel_state.is_collapsed()) {
return;
}
for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
if (socket_decl->in_out == SOCK_IN) {
ui_node_draw_input(layout,
C,
ntree,
node,
node.socket_by_decl(*socket_decl),
depth,
panel_decl.name.c_str());
}
}
else if (const auto *sub_panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl))
{
ui_node_draw_recursive(layout, C, ntree, node, *sub_panel_decl, depth + 1);
}
else if (const auto *layout_decl = dynamic_cast<const nodes::LayoutDeclaration *>(item_decl)) {
PointerRNA nodeptr = RNA_pointer_create(&ntree.id, &RNA_Node, &node);
layout_decl->draw(&layout, &C, &nodeptr);
}
}
}
@@ -797,51 +824,29 @@ static void ui_node_draw_node(
{
PointerRNA nodeptr = RNA_pointer_create(&ntree.id, &RNA_Node, &node);
if (node.typeinfo->draw_buttons) {
if (node.type != NODE_GROUP) {
uiLayoutSetPropSep(&layout, true);
node.typeinfo->draw_buttons(&layout, &C, &nodeptr);
}
}
if (node.declaration() && node.declaration()->use_custom_socket_order) {
/* Node with panels. */
namespace nodes = blender::nodes;
using ItemDeclIterator = blender::Span<nodes::ItemDeclarationPtr>::iterator;
using SocketIterator = blender::Span<bNodeSocket *>::iterator;
using PanelStateIterator = blender::MutableSpan<bNodePanelState>::iterator;
ItemDeclIterator item_decl = node.declaration()->all_items.begin();
SocketIterator input = node.input_sockets().begin();
PanelStateIterator panel_state = node.panel_states().begin();
const ItemDeclIterator item_decl_end = node.declaration()->all_items.end();
bool panel_collapsed = false;
const char *panel_label = nullptr;
for (; item_decl != item_decl_end; ++item_decl) {
if (const nodes::SocketDeclaration *socket_decl =
dynamic_cast<const nodes::SocketDeclaration *>(item_decl->get()))
const nodes::NodeDeclaration &node_decl = *node.declaration();
for (const nodes::ItemDeclaration *item_decl : node_decl.root_items) {
if (const auto *panel_decl = dynamic_cast<const nodes::PanelDeclaration *>(item_decl)) {
ui_node_draw_recursive(layout, C, ntree, node, *panel_decl, depth + 1);
}
else if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl))
{
if (socket_decl->in_out == SOCK_IN) {
if (!panel_collapsed) {
ui_node_draw_input(layout, C, ntree, node, **input, depth + 1, panel_label);
}
++input;
ui_node_draw_input(
layout, C, ntree, node, node.socket_by_decl(*socket_decl), depth, nullptr);
}
}
else if (const nodes::PanelDeclaration *panel_decl =
dynamic_cast<const nodes::PanelDeclaration *>(item_decl->get()))
{
panel_collapsed = panel_state->is_collapsed();
panel_label = panel_decl->name.c_str();
ui_node_draw_panel(layout, C, ntree, *panel_decl, *panel_state, nodeptr);
++panel_state;
}
}
}
else {
/* Node without panels. */
if (node.typeinfo->draw_buttons) {
if (node.type != NODE_GROUP) {
uiLayoutSetPropSep(&layout, true);
node.typeinfo->draw_buttons(&layout, &C, &nodeptr);
}
}
LISTBASE_FOREACH (bNodeSocket *, input, &node.inputs) {
ui_node_draw_input(layout, C, ntree, node, *input, depth + 1, nullptr);
}

View File

@@ -489,6 +489,9 @@ typedef struct bNode {
const bNodeSocket &output_by_identifier(blender::StringRef identifier) const;
bNodeSocket &input_by_identifier(blender::StringRef identifier);
bNodeSocket &output_by_identifier(blender::StringRef identifier);
/** Lookup socket by its declaration. */
const bNodeSocket &socket_by_decl(const blender::nodes::SocketDeclaration &decl) const;
bNodeSocket &socket_by_decl(const blender::nodes::SocketDeclaration &decl);
/** If node is frame, will return all children nodes. */
blender::Span<bNode *> direct_children_in_frame() const;
blender::Span<bNodePanelState> panel_states() const;

View File

@@ -190,6 +190,9 @@ class SocketDeclaration : public ItemDeclaration {
/** Puts this socket on the same line as the previous one in the UI. */
bool align_with_previous_socket = false;
/** Index in the list of inputs or outputs of the node. */
int index = -1;
InputSocketFieldType input_field_type = InputSocketFieldType::None;
OutputFieldDependency output_field_dependency;
@@ -257,8 +260,6 @@ class PanelDeclarationBuilder;
class BaseSocketDeclarationBuilder {
protected:
/* Index of the socket in the list of inputs or outputs. */
int index_ = -1;
bool reference_pass_all_ = false;
bool field_on_all_ = false;
bool propagate_from_all_ = false;
@@ -414,10 +415,20 @@ class SocketDeclarationBuilder : public BaseSocketDeclarationBuilder {
using SocketDeclarationPtr = std::unique_ptr<SocketDeclaration>;
using PanelDrawButtonsFunction = void (*)(uiLayout *, bContext *, PointerRNA *);
using DrawNodeLayoutFn = void(uiLayout *, bContext *, PointerRNA *);
class SeparatorDeclaration : public ItemDeclaration {};
class LayoutDeclaration : public ItemDeclaration {
public:
std::function<DrawNodeLayoutFn> draw;
/**
* Sometimes the default layout has special handling (e.g. choose between #draw_buttons and
* #draw_buttons_ex).
*/
bool is_default = false;
};
/**
* Describes a panel containing sockets or other panels.
*/
@@ -428,8 +439,10 @@ class PanelDeclaration : public ItemDeclaration {
std::string description;
std::string translation_context;
bool default_collapsed = false;
PanelDrawButtonsFunction draw_buttons = nullptr;
Vector<ItemDeclaration *> items;
/** Index in the list of panels on the node. */
int index = -1;
PanelDeclaration *parent_panel = nullptr;
private:
friend NodeDeclarationBuilder;
@@ -441,6 +454,8 @@ class PanelDeclaration : public ItemDeclaration {
void build(bNodePanelState &panel) const;
bool matches(const bNodePanelState &panel) const;
void update_or_build(const bNodePanelState &old_panel, bNodePanelState &new_panel) const;
int depth() const;
};
/**
@@ -451,6 +466,7 @@ class DeclarationListBuilder {
public:
NodeDeclarationBuilder &node_decl_builder;
Vector<ItemDeclaration *> &items;
PanelDeclaration *parent_panel_decl = nullptr;
DeclarationListBuilder(NodeDeclarationBuilder &node_decl_builder,
Vector<ItemDeclaration *> &items)
@@ -484,6 +500,8 @@ class DeclarationListBuilder {
PanelDeclarationBuilder &add_panel(StringRef name, int identifier = -1);
void add_separator();
void add_default_layout();
void add_layout(std::function<void(uiLayout *, bContext *, PointerRNA *)> draw);
};
class PanelDeclarationBuilder : public DeclarationListBuilder {
@@ -497,11 +515,11 @@ class PanelDeclarationBuilder : public DeclarationListBuilder {
PanelDeclarationBuilder(NodeDeclarationBuilder &node_builder, PanelDeclaration &decl)
: DeclarationListBuilder(node_builder, decl.items), decl_(&decl)
{
this->parent_panel_decl = &decl;
}
Self &description(std::string value = "");
Self &default_closed(bool closed);
Self &draw_buttons(PanelDrawButtonsFunction func);
};
using PanelDeclarationPtr = std::unique_ptr<PanelDeclaration>;
@@ -515,6 +533,7 @@ class NodeDeclaration {
/* All input and output socket declarations. */
Vector<SocketDeclaration *> inputs;
Vector<SocketDeclaration *> outputs;
Vector<PanelDeclaration *> panels;
std::unique_ptr<aal::RelationsInNode> anonymous_attribute_relations_;
/** Leave the sockets in place, even if they don't match the declaration. Used for dynamic
@@ -553,6 +572,7 @@ class NodeDeclaration {
class NodeDeclarationBuilder : public DeclarationListBuilder {
private:
const bke::bNodeType &typeinfo_;
NodeDeclaration &declaration_;
const bNodeTree *ntree_ = nullptr;
const bNode *node_ = nullptr;
@@ -566,7 +586,8 @@ class NodeDeclarationBuilder : public DeclarationListBuilder {
friend DeclarationListBuilder;
public:
NodeDeclarationBuilder(NodeDeclaration &declaration,
NodeDeclarationBuilder(const bke::bNodeType &typeinfo,
NodeDeclaration &declaration,
const bNodeTree *ntree = nullptr,
const bNode *node = nullptr);
@@ -686,12 +707,12 @@ inline typename DeclType::Builder &DeclarationListBuilder::add_socket(StringRef
if (in_out == SOCK_IN) {
this->node_decl_builder.input_socket_builders_.append(&socket_decl_builder);
socket_decl_builder.index_ = this->node_decl_builder.declaration_.inputs.append_and_get_index(
socket_decl.index = this->node_decl_builder.declaration_.inputs.append_and_get_index(
&socket_decl);
}
else {
this->node_decl_builder.output_socket_builders_.append(&socket_decl_builder);
socket_decl_builder.index_ = this->node_decl_builder.declaration_.outputs.append_and_get_index(
socket_decl.index = this->node_decl_builder.declaration_.outputs.append_and_get_index(
&socket_decl);
}
return socket_decl_builder;
@@ -705,7 +726,7 @@ inline typename DeclType::Builder &DeclarationListBuilder::add_socket(StringRef
inline int BaseSocketDeclarationBuilder::index() const
{
return index_;
return decl_base_->index;
}
inline bool BaseSocketDeclarationBuilder::is_input() const

View File

@@ -31,6 +31,8 @@ static void node_declare(NodeDeclarationBuilder &b)
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.add_input<decl::Geometry>("Geometry");
b.add_output<decl::Geometry>("Geometry").propagate_all().align_with_previous();
if (node != nullptr) {

View File

@@ -51,6 +51,8 @@ static void node_declare(NodeDeclarationBuilder &b)
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
const bNodeTree *ntree = b.tree_or_null();
const bNode *node = b.node_or_null();
if (!node) {

View File

@@ -104,6 +104,8 @@ static void node_declare(NodeDeclarationBuilder &b)
const bNode *node = b.node_or_null();
const bNodeTree *tree = b.tree_or_null();
b.add_default_layout();
if (!node || !tree) {
return;
}

View File

@@ -18,6 +18,8 @@ static void node_declare(NodeDeclarationBuilder &b)
b.use_custom_socket_order();
b.allow_any_socket_order();
b.add_default_layout();
b.add_input<decl::Bool>("Show").default_value(true).hide_value();
b.add_output<decl::Bool>("Show").align_with_previous();
b.add_input<decl::String>("Message").hide_label();

View File

@@ -402,27 +402,42 @@ static void set_default_input_field(const bNodeTreeInterfaceSocket &input, Socke
static void node_group_declare_panel_recursive(DeclarationListBuilder &b,
const bNodeTree &group,
const bNodeTreeInterfacePanel &io_parent_panel)
const bNodeTreeInterfacePanel &io_parent_panel,
const bool is_root)
{
bool layout_added = false;
auto add_layout_if_needed = [&]() {
if (is_root && !layout_added) {
b.add_default_layout();
layout_added = true;
}
};
for (const bNodeTreeInterfaceItem *item : io_parent_panel.items()) {
switch (item->item_type) {
case NODE_INTERFACE_SOCKET: {
const auto &io_socket = node_interface::get_item_as<bNodeTreeInterfaceSocket>(*item);
const eNodeSocketInOut in_out = (io_socket.flag & NODE_INTERFACE_SOCKET_INPUT) ? SOCK_IN :
SOCK_OUT;
if (in_out == SOCK_IN) {
add_layout_if_needed();
}
build_interface_socket_declaration(group, io_socket, in_out, b);
break;
}
case NODE_INTERFACE_PANEL: {
add_layout_if_needed();
const auto &io_panel = node_interface::get_item_as<bNodeTreeInterfacePanel>(*item);
auto &panel_b = b.add_panel(StringRef(io_panel.name), io_panel.identifier)
.description(StringRef(io_panel.description))
.default_closed(io_panel.flag & NODE_INTERFACE_PANEL_DEFAULT_CLOSED);
node_group_declare_panel_recursive(panel_b, group, io_panel);
node_group_declare_panel_recursive(panel_b, group, io_panel, false);
break;
}
}
}
add_layout_if_needed();
}
void node_group_declare(NodeDeclarationBuilder &b)
@@ -445,7 +460,7 @@ void node_group_declare(NodeDeclarationBuilder &b)
/* Allow the node group interface to define the socket order. */
r_declaration.use_custom_socket_order = true;
node_group_declare_panel_recursive(b, *group, group->tree_interface.root_panel);
node_group_declare_panel_recursive(b, *group, group->tree_interface.root_panel, true);
if (group->type == NTREE_GEOMETRY) {
group->ensure_interface_cache();

View File

@@ -30,7 +30,7 @@ void build_node_declaration(const bke::bNodeType &typeinfo,
const bNode *node)
{
reset_declaration(r_declaration);
NodeDeclarationBuilder node_decl_builder{r_declaration, ntree, node};
NodeDeclarationBuilder node_decl_builder{typeinfo, r_declaration, ntree, node};
typeinfo.declare(node_decl_builder);
node_decl_builder.finalize();
}
@@ -57,7 +57,7 @@ void NodeDeclarationBuilder::build_remaining_anonymous_attribute_relations()
for (BaseSocketDeclarationBuilder *socket_builder : input_socket_builders_) {
if (socket_builder->field_on_all_) {
aal::RelationsInNode &relations = this->get_anonymous_attribute_relations();
const int field_input = socket_builder->index_;
const int field_input = socket_builder->decl_base_->index;
for (const int geometry_input : geometry_inputs) {
relations.eval_relations.append({field_input, geometry_input});
}
@@ -66,14 +66,14 @@ void NodeDeclarationBuilder::build_remaining_anonymous_attribute_relations()
for (BaseSocketDeclarationBuilder *socket_builder : output_socket_builders_) {
if (socket_builder->field_on_all_) {
aal::RelationsInNode &relations = this->get_anonymous_attribute_relations();
const int field_output = socket_builder->index_;
const int field_output = socket_builder->decl_base_->index;
for (const int geometry_output : geometry_outputs) {
relations.available_relations.append({field_output, geometry_output});
}
}
if (socket_builder->reference_pass_all_) {
aal::RelationsInNode &relations = this->get_anonymous_attribute_relations();
const int field_output = socket_builder->index_;
const int field_output = socket_builder->decl_base_->index;
for (const int input_i : declaration_.inputs.index_range()) {
SocketDeclaration &input_socket_decl = *declaration_.inputs[input_i];
if (input_socket_decl.input_field_type != InputSocketFieldType::None) {
@@ -83,7 +83,7 @@ void NodeDeclarationBuilder::build_remaining_anonymous_attribute_relations()
}
if (socket_builder->propagate_from_all_) {
aal::RelationsInNode &relations = this->get_anonymous_attribute_relations();
const int geometry_output = socket_builder->index_;
const int geometry_output = socket_builder->decl_base_->index;
for (const int geometry_input : geometry_inputs) {
relations.propagate_relations.append({geometry_input, geometry_output});
}
@@ -99,10 +99,12 @@ void NodeDeclarationBuilder::finalize()
#endif
}
NodeDeclarationBuilder::NodeDeclarationBuilder(NodeDeclaration &declaration,
NodeDeclarationBuilder::NodeDeclarationBuilder(const bke::bNodeType &typeinfo,
NodeDeclaration &declaration,
const bNodeTree *ntree,
const bNode *node)
: DeclarationListBuilder(*this, declaration.root_items),
typeinfo_(typeinfo),
declaration_(declaration),
ntree_(ntree),
node_(node)
@@ -236,8 +238,10 @@ bool NodeDeclaration::matches(const bNode &node) const
}
++current_panel;
}
else if (dynamic_cast<const SeparatorDeclaration *>(item_decl.get())) {
/* Separators are ignored here because they don't have corresponding data in DNA. */
else if (dynamic_cast<const SeparatorDeclaration *>(item_decl.get()) ||
dynamic_cast<const LayoutDeclaration *>(item_decl.get()))
{
/* Ignored because they don't have corresponding data in DNA. */
}
else {
/* Unknown item type. */
@@ -416,6 +420,26 @@ void DeclarationListBuilder::add_separator()
this->items.append(&decl);
}
void DeclarationListBuilder::add_default_layout()
{
BLI_assert(this->node_decl_builder.typeinfo_.draw_buttons);
this->add_layout([](uiLayout *layout, bContext *C, PointerRNA *ptr) {
const bNode &node = *static_cast<bNode *>(ptr->data);
node.typeinfo->draw_buttons(layout, C, ptr);
});
static_cast<LayoutDeclaration &>(*this->items.last()).is_default = true;
}
void DeclarationListBuilder::add_layout(
std::function<void(uiLayout *, bContext *, PointerRNA *)> draw)
{
auto decl_ptr = std::make_unique<LayoutDeclaration>();
LayoutDeclaration &decl = *decl_ptr;
decl.draw = std::move(draw);
this->node_decl_builder.declaration_.all_items.append(std::move(decl_ptr));
this->items.append(&decl);
}
PanelDeclarationBuilder &DeclarationListBuilder::add_panel(const StringRef name, int identifier)
{
auto panel_decl_ptr = std::make_unique<PanelDeclaration>();
@@ -432,8 +456,10 @@ PanelDeclarationBuilder &DeclarationListBuilder::add_panel(const StringRef name,
panel_decl.identifier = this->node_decl_builder.declaration_.all_items.size();
}
panel_decl.name = name;
panel_decl.parent_panel = this->parent_panel_decl;
panel_decl.index = this->node_decl_builder.declaration_.panels.append_and_get_index(&panel_decl);
this->node_decl_builder.declaration_.all_items.append(std::move(panel_decl_ptr));
this->node_decl_builder.panel_builders_.append(std::move(panel_decl_builder_ptr));
this->node_decl_builder.panel_builders_.append_and_get_index(std::move(panel_decl_builder_ptr));
this->items.append(&panel_decl);
return panel_decl_builder;
}
@@ -458,6 +484,16 @@ void PanelDeclaration::update_or_build(const bNodePanelState &old_panel,
SET_FLAG_FROM_TEST(new_panel.flag, old_panel.is_collapsed(), NODE_PANEL_COLLAPSED);
}
int PanelDeclaration::depth() const
{
int count = 0;
for (const PanelDeclaration *parent = this->parent_panel; parent; parent = parent->parent_panel)
{
count++;
}
return count;
}
BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::supports_field()
{
BLI_assert(this->is_input());
@@ -508,7 +544,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::reference_pass(
for (const int from_input : input_indices) {
aal::ReferenceRelation relation;
relation.from_field_input = from_input;
relation.to_field_output = index_;
relation.to_field_output = decl_base_->index;
relations.reference_relations.append(relation);
}
return *this;
@@ -521,7 +557,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::field_on(const Span<
this->supports_field();
for (const int input_index : indices) {
aal::EvalRelation relation;
relation.field_input = index_;
relation.field_input = decl_base_->index;
relation.geometry_input = input_index;
relations.eval_relations.append(relation);
}
@@ -530,7 +566,7 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::field_on(const Span<
this->field_source();
for (const int output_index : indices) {
aal::AvailableRelation relation;
relation.field_output = index_;
relation.field_output = decl_base_->index;
relation.geometry_output = output_index;
relations.available_relations.append(relation);
}
@@ -783,12 +819,6 @@ PanelDeclarationBuilder &PanelDeclarationBuilder::default_closed(bool closed)
return *this;
}
PanelDeclarationBuilder &PanelDeclarationBuilder::draw_buttons(PanelDrawButtonsFunction func)
{
decl_->draw_buttons = func;
return *this;
}
namespace implicit_field_inputs {
void position(const bNode & /*node*/, void *r_value)

View File

@@ -81,12 +81,10 @@ static void node_declare(NodeDeclarationBuilder &b)
#define SOCK_DIFFUSE_ROUGHNESS_ID 7
/* Panel for Subsurface scattering settings. */
PanelDeclarationBuilder &sss =
b.add_panel("Subsurface")
.default_closed(true)
.draw_buttons([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) {
uiItemR(layout, ptr, "subsurface_method", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
});
PanelDeclarationBuilder &sss = b.add_panel("Subsurface").default_closed(true);
sss.add_layout([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) {
uiItemR(layout, ptr, "subsurface_method", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
});
sss.add_input<decl::Float>("Subsurface Weight")
.default_value(0.0f)
.min(0.0f)
@@ -134,12 +132,10 @@ static void node_declare(NodeDeclarationBuilder &b)
#define SOCK_SUBSURFACE_ANISOTROPY_ID 12
/* Panel for Specular settings. */
PanelDeclarationBuilder &spec =
b.add_panel("Specular")
.default_closed(true)
.draw_buttons([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) {
uiItemR(layout, ptr, "distribution", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
});
PanelDeclarationBuilder &spec = b.add_panel("Specular").default_closed(true);
spec.add_layout([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) {
uiItemR(layout, ptr, "distribution", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
});
spec.add_input<decl::Float>("Specular IOR Level")
.default_value(0.5f)
.min(0.0f)