From 2f1b8f59e37f3cc7e77b3a7f6b964280fe8a7947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20T=C3=B6nne?= Date: Wed, 27 Sep 2023 11:48:40 +0200 Subject: [PATCH] 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 --- .../blender/editors/space_node/node_draw.cc | 223 +++++++++++------- 1 file changed, 144 insertions(+), 79 deletions(-) diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 9d87121572a..80b814479eb 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -616,73 +616,116 @@ static Vector 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::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, + 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 panel_updates; /* Only true for the first item in the layout. */ bool is_first = true; - const Vector 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 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 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; } }