diff --git a/source/blender/blenkernel/BKE_node_runtime.hh b/source/blender/blenkernel/BKE_node_runtime.hh index 2b909966e98..65a59a05084 100644 --- a/source/blender/blenkernel/BKE_node_runtime.hh +++ b/source/blender/blenkernel/BKE_node_runtime.hh @@ -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 header_center_y; + std::optional content_extent; }; /** diff --git a/source/blender/blenkernel/intern/node_runtime.cc b/source/blender/blenkernel/intern/node_runtime.cc index 43eec396d62..b7230d8f256 100644 --- a/source/blender/blenkernel/intern/node_runtime.cc +++ b/source/blender/blenkernel/intern/node_runtime.cc @@ -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); +} diff --git a/source/blender/editors/interface/templates/interface_template_node_inputs.cc b/source/blender/editors/interface/templates/interface_template_node_inputs.cc index 4699f8eeaf1..122ede00c39 100644 --- a/source/blender/editors/interface/templates/interface_template_node_inputs.cc +++ b/source/blender/editors/interface/templates/interface_template_node_inputs.cc @@ -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(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); + /* 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(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( - 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(item_decl)) { + draw_node_inputs_recursive(C, panel_layout, node, node_ptr, *sub_panel_decl); + } + else if (const auto *layout_decl = dynamic_cast(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(ptr->owner_id); bNode &node = *static_cast(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(item_decl)) { + blender::ui::nodes::draw_node_inputs_recursive(C, layout, node, ptr, *panel_decl); + } + else if (const auto *socket_decl = dynamic_cast(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(item_decl)) { + if (!layout_decl->is_default) { + layout_decl->draw(layout, C, ptr); + } + } + } } else { /* Draw socket values using the flat inputs list. */ diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 29bf2be0c4c..48a84eee516 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -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 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 + 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 node_build_item_data(bNode &node) +static void determine_potentially_visible_panels_recursive( + const bNode &node, const nodes::PanelDeclaration &panel_decl, MutableSpan r_result) { - namespace nodes = blender::nodes; - using ItemDeclIterator = blender::Span::iterator; - using SocketIterator = blender::Span::iterator; - using PanelStateIterator = blender::MutableSpan::iterator; - using PanelRuntimeIterator = blender::MutableSpan::iterator; + bool potentially_visible = false; + for (const nodes::ItemDeclaration *item_decl : panel_decl.items) { + if (const auto *socket_decl = dynamic_cast(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(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 r_result) +{ + for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) { + if (const auto *panel_decl = dynamic_cast(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 potentially_visible_states, + MutableSpan 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(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 potentially_visible_states, + MutableSpan r_result) +{ + for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) { + if (const auto *panel_decl = dynamic_cast(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 r_visibility_states) +{ + Array 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 &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(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 &r_items) +{ + r_items.append({flat_item::Separator()}); +} + +static void add_flat_items_for_layout(const bNode &node, + const nodes::LayoutDeclaration &layout_decl, + Vector &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 panel_visibility, + Vector &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(item_decl)) { + add_flat_items_for_socket(node, *socket_decl, &panel_decl, r_items); + } + else if (const auto *sub_panel_decl = dynamic_cast(item_decl)) + { + add_flat_items_for_panel(node, *sub_panel_decl, panel_visibility, r_items); + } + else if (dynamic_cast(item_decl)) { + add_flat_items_for_separator(r_items); + } + else if (const auto *layout_decl = dynamic_cast(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 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 panel_visibility(panels_num, false); + determine_visible_panels(node, panel_visibility); - Vector result; - result.reserve(node.declaration()->all_items.size()); - - while (item_decl != item_decl_end) { - if (const nodes::SocketDeclaration *socket_decl = - dynamic_cast(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 items; + for (const nodes::ItemDeclaration *item_decl : node.declaration()->root_items) { + if (const auto *socket_decl = dynamic_cast(item_decl)) { + add_flat_items_for_socket(node, *socket_decl, nullptr, items); } - else if (const nodes::PanelDeclaration *panel_decl = - dynamic_cast(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(item_decl)) { + add_flat_items_for_panel(node, *panel_decl, panel_visibility, items); } - else if (dynamic_cast(item_decl->get())) { - result.append(NodeInterfaceItemData::separator()); - ++item_decl; + else if (dynamic_cast(item_decl)) { + add_flat_items_for_separator(items); + } + else if (const auto *layout_decl = dynamic_cast(item_decl)) { + add_flat_items_for_layout(node, *layout_decl, items); } } - return result; + return items; } -using ItemIterator = Vector::const_iterator; - -struct VisibilityUpdateState { - ItemIterator item_iter; - const ItemIterator item_end; - - explicit VisibilityUpdateState(const Span 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 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 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 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(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(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 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(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(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 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(&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 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 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; + if constexpr (std::is_same_v) { + 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) { + 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(&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) { + 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) { + 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) { + 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) { + 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( - 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( - 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(&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(&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); } } diff --git a/source/blender/editors/space_node/node_templates.cc b/source/blender/editors/space_node/node_templates.cc index bb98a9d2434..e5b6dd9e97d 100644 --- a/source/blender/editors/space_node/node_templates.cc +++ b/source/blender/editors/space_node/node_templates.cc @@ -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(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(item_decl)) + { + ui_node_draw_recursive(layout, C, ntree, node, *sub_panel_decl, depth + 1); + } + else if (const auto *layout_decl = dynamic_cast(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::iterator; - using SocketIterator = blender::Span::iterator; - using PanelStateIterator = blender::MutableSpan::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(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(item_decl)) { + ui_node_draw_recursive(layout, C, ntree, node, *panel_decl, depth + 1); + } + else if (const auto *socket_decl = dynamic_cast(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(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); } diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index f8d669a763c..e9762345c2b 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -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 direct_children_in_frame() const; blender::Span panel_states() const; diff --git a/source/blender/nodes/NOD_node_declaration.hh b/source/blender/nodes/NOD_node_declaration.hh index c71d4f813a8..302601c4cb8 100644 --- a/source/blender/nodes/NOD_node_declaration.hh +++ b/source/blender/nodes/NOD_node_declaration.hh @@ -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; -using PanelDrawButtonsFunction = void (*)(uiLayout *, bContext *, PointerRNA *); +using DrawNodeLayoutFn = void(uiLayout *, bContext *, PointerRNA *); class SeparatorDeclaration : public ItemDeclaration {}; +class LayoutDeclaration : public ItemDeclaration { + public: + std::function 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 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 &items; + PanelDeclaration *parent_panel_decl = nullptr; DeclarationListBuilder(NodeDeclarationBuilder &node_decl_builder, Vector &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 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; @@ -515,6 +533,7 @@ class NodeDeclaration { /* All input and output socket declarations. */ Vector inputs; Vector outputs; + Vector panels; std::unique_ptr 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 diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc index d50955f7078..2fbcd5f1ab2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -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("Geometry"); b.add_output("Geometry").propagate_all().align_with_previous(); if (node != nullptr) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_bake.cc b/source/blender/nodes/geometry/nodes/node_geo_bake.cc index 4360361098a..b8db69c8431 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_bake.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_bake.cc @@ -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) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_foreach_geometry_element.cc b/source/blender/nodes/geometry/nodes/node_geo_foreach_geometry_element.cc index 4fe00de9815..646d308b6d9 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_foreach_geometry_element.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_foreach_geometry_element.cc @@ -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; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_warning.cc b/source/blender/nodes/geometry/nodes/node_geo_warning.cc index e6b18680ce2..cb53b17ab2d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_warning.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_warning.cc @@ -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("Show").default_value(true).hide_value(); b.add_output("Show").align_with_previous(); b.add_input("Message").hide_label(); diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index ade0bf2f73f..7fc8de443a7 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -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(*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(*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(); diff --git a/source/blender/nodes/intern/node_declaration.cc b/source/blender/nodes/intern/node_declaration.cc index 647a50c4901..3ca4826d258 100644 --- a/source/blender/nodes/intern/node_declaration.cc +++ b/source/blender/nodes/intern/node_declaration.cc @@ -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(item_decl.get())) { - /* Separators are ignored here because they don't have corresponding data in DNA. */ + else if (dynamic_cast(item_decl.get()) || + dynamic_cast(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(ptr->data); + node.typeinfo->draw_buttons(layout, C, ptr); + }); + static_cast(*this->items.last()).is_default = true; +} + +void DeclarationListBuilder::add_layout( + std::function draw) +{ + auto decl_ptr = std::make_unique(); + 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(); @@ -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) diff --git a/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.cc b/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.cc index 85c410c7075..a92e25390bb 100644 --- a/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.cc +++ b/source/blender/nodes/shader/nodes/node_shader_bsdf_principled.cc @@ -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("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("Specular IOR Level") .default_value(0.5f) .min(0.0f)