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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user