Cleanup: Nodes: Simplify drawing function

The node drawing function currently uses a stack-based traversal of
interface items. This is hard to read and for this purpose a recursive
function is easier to understand.

Pull Request: https://projects.blender.org/blender/blender/pulls/112860
This commit is contained in:
Lukas Tönne
2023-09-27 11:48:40 +02:00
parent 1da08a5556
commit 2f1b8f59e3

View File

@@ -616,73 +616,116 @@ static Vector<NodeInterfaceItemData> node_build_item_data(bNode &node)
return result;
}
/* Advanced drawing with panels and arbitrary input/output ordering. */
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;
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,
VisibilityUpdateState &state)
{
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;
}
else if (num_items > 0) {
--num_items;
}
/* Consume item. */
const NodeInterfaceItemData &item = *state.item_iter++;
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->num_child_decls, is_collapsed, state);
}
else if (item.is_valid_socket()) {
if (item.input) {
SET_FLAG_FROM_TEST(item.input->flag, is_parent_collapsed, SOCK_PANEL_COLLAPSED);
}
if (item.output) {
SET_FLAG_FROM_TEST(item.output->flag, is_parent_collapsed, SOCK_PANEL_COLLAPSED);
}
}
else {
/* Should not happen. */
BLI_assert_unreachable();
}
}
}
struct LocationUpdateState {
ItemIterator item_iter;
const ItemIterator item_end;
BLI_assert(is_node_panels_supported(node));
BLI_assert(node.runtime->panels.size() == node.num_panel_states);
/* Checked at various places to avoid adding duplicate spacers without anything in between. */
bool need_spacer_after_item = false;
/* Space at the top. */
locy -= NODE_DYS / 2;
/* Makes sure buttons are only drawn once. */
bool buttons_drawn = false;
/* The panel stack keeps track of the hierarchy of panels. When a panel declaration is found a
* new #PanelUpdate is added to the stack. Items in the declaration are added to the top panel
* of the stack. Each panel expects a number of items to be added, after which the panel is
* removed from the stack again. */
struct PanelUpdate {
/* How many declarations still to add. */
int remaining_decls;
/* True if the panel or its parent is collapsed. */
bool is_collapsed;
/* Panel label for shortening socket labels. */
const char *label = nullptr;
/* Location data, needed to finalize the panel when all items have been added. */
bke::bNodePanelRuntime *runtime;
};
Stack<PanelUpdate> panel_updates;
/* Only true for the first item in the layout. */
bool is_first = true;
const Vector<NodeInterfaceItemData> item_data = node_build_item_data(node);
for (const NodeInterfaceItemData &item : item_data) {
bool is_parent_collapsed = false;
const char *parent_label = nullptr;
if (PanelUpdate *parent_update = panel_updates.is_empty() ? nullptr : &panel_updates.peek()) {
/* Adding an item to the parent panel, will be popped when reaching 0. */
BLI_assert(parent_update->remaining_decls > 0);
--parent_update->remaining_decls;
is_parent_collapsed = parent_update->is_collapsed;
parent_label = parent_update->label;
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)
{
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;
}
else if (num_items > 0) {
--num_items;
}
/* Consume item. */
const NodeInterfaceItemData &item = *state.item_iter++;
if (item.is_valid_panel()) {
/* Draw buttons before the first panel. */
if (!buttons_drawn) {
buttons_drawn = true;
need_spacer_after_item = node_update_basis_buttons(
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) {
locy -= NODE_DY;
is_first = false;
state.is_first = false;
}
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();
panel_updates.push({item.panel_decl->num_child_decls,
is_collapsed,
item.panel_decl->name.c_str(),
item.runtime});
/* Round the socket location to stop it from jiggling. */
item.runtime->location_y = round(locy + NODE_DYS);
@@ -694,14 +737,25 @@ static void node_update_basis_from_declaration(
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->num_child_decls,
is_collapsed,
item.panel_decl->name.c_str(),
item.runtime,
state);
}
else if (item.is_valid_socket()) {
if (item.input) {
SET_FLAG_FROM_TEST(item.input->flag, is_parent_collapsed, SOCK_PANEL_COLLAPSED);
/* Draw buttons before the first input. */
if (!buttons_drawn) {
buttons_drawn = true;
need_spacer_after_item = node_update_basis_buttons(
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);
}
@@ -710,20 +764,19 @@ static void node_update_basis_from_declaration(
}
else {
/* Space between items. */
if (!is_first && item.input->is_visible()) {
if (!state.is_first && item.input->is_visible()) {
locy -= NODE_ITEM_SPACING_Y;
}
}
}
if (item.output) {
SET_FLAG_FROM_TEST(item.output->flag, is_parent_collapsed, SOCK_PANEL_COLLAPSED);
if (is_parent_collapsed) {
item.output->runtime->location = float2(round(locx + NODE_WIDTH(node)),
round(locy + NODE_DYS));
}
else {
/* Space between items. */
if (!is_first && item.output->is_visible()) {
if (!state.is_first && item.output->is_visible()) {
locy -= NODE_ITEM_SPACING_Y;
}
}
@@ -733,44 +786,56 @@ static void node_update_basis_from_declaration(
node_update_basis_socket(
C, ntree, node, parent_label, item.input, item.output, block, locx, locy))
{
is_first = false;
need_spacer_after_item = true;
state.is_first = false;
state.need_spacer_after_item = true;
}
}
else {
/* Should not happen. */
BLI_assert_unreachable();
}
/* Close parent panels that have all items added. */
while (!panel_updates.is_empty()) {
PanelUpdate &top_panel = panel_updates.peek();
if (top_panel.remaining_decls > 0) {
/* Incomplete panel, continue adding items. */
break;
}
if (!top_panel.is_collapsed) {
/* Finalize the vertical extent of the content. */
locy -= 2 * NODE_ITEM_SPACING_Y; /* Space at bottom of panel contents. */
top_panel.runtime->min_content_y = round(locy);
locy -= NODE_ITEM_SPACING_Y / 2; /* Space at top of next panel header. */
}
/* Close panel and continue checking parent. */
panel_updates.pop();
}
}
/* Enough items should have been added to close all panels. */
BLI_assert(panel_updates.is_empty());
/* Finalize the vertical extent of the content. */
if (!is_parent_collapsed) {
locy -= 2 * NODE_ITEM_SPACING_Y; /* Space at bottom of panel contents. */
if (parent_runtime) {
parent_runtime->min_content_y = round(locy);
}
locy -= NODE_ITEM_SPACING_Y / 2; /* Space at top of next panel header. */
}
};
/* Advanced drawing with panels and arbitrary input/output ordering. */
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);
node_update_panel_items_visibility_recursive(-1, false, visibility_state);
/* Space at the top. */
locy -= NODE_DYS / 2;
/* Start by adding root panel items. */
LocationUpdateState location_state(item_data);
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 (!buttons_drawn) {
need_spacer_after_item = node_update_basis_buttons(
if (!location_state.buttons_drawn) {
location_state.need_spacer_after_item = node_update_basis_buttons(
C, ntree, node, node.typeinfo->draw_buttons, block, locy);
}
if (need_spacer_after_item) {
if (location_state.need_spacer_after_item) {
locy -= NODE_DYS / 2;
}
}