Nodes: support boolean inputs as toggles in panel headers
Adds the option to create a boolean socket that can be used as a panel toggle. This allows creating simpler and more compact node group UIs when a panel can be "disabled". The toggle input is a normal input socket that is just drawn a bit differently in the UI. Whether a boolean is a toggle input or not does not affect evaluation. Also see #133936 for guides on how to add and remove panel toggles. Pull Request: https://projects.blender.org/blender/blender/pulls/133936
This commit is contained in:
committed by
Jacques Lucke
parent
21e14163c6
commit
2822777f13
@@ -301,15 +301,29 @@ class NODE_OT_interface_item_new(NodeInterfaceOperator, Operator):
|
||||
bl_label = "New Item"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
item_type: EnumProperty(
|
||||
name="Item Type",
|
||||
description="Type of the item to create",
|
||||
items=(
|
||||
def get_items(_self, context):
|
||||
snode = context.space_data
|
||||
tree = snode.edit_tree
|
||||
interface = tree.interface
|
||||
|
||||
items = [
|
||||
('INPUT', "Input", ""),
|
||||
('OUTPUT', "Output", ""),
|
||||
('PANEL', "Panel", ""),
|
||||
),
|
||||
default='INPUT',
|
||||
]
|
||||
|
||||
active_item = interface.active
|
||||
# Panels have the extra option to add a toggle.
|
||||
if active_item and active_item.item_type == 'PANEL':
|
||||
items.append(('PANEL_TOGGLE', "Panel Toggle", ""))
|
||||
|
||||
return items
|
||||
|
||||
item_type: EnumProperty(
|
||||
name="Item Type",
|
||||
description="Type of the item to create",
|
||||
items=get_items,
|
||||
default=0,
|
||||
)
|
||||
|
||||
# Returns a valid socket type for the given tree or None.
|
||||
@@ -347,6 +361,18 @@ class NODE_OT_interface_item_new(NodeInterfaceOperator, Operator):
|
||||
item = interface.new_socket("Socket", socket_type=self.find_valid_socket_type(tree), in_out='OUTPUT')
|
||||
elif self.item_type == 'PANEL':
|
||||
item = interface.new_panel("Panel")
|
||||
elif self.item_type == 'PANEL_TOGGLE':
|
||||
active_panel = active_item
|
||||
if len(active_panel.interface_items) > 0:
|
||||
first_item = active_panel.interface_items[0]
|
||||
if type(first_item) is bpy.types.NodeTreeInterfaceSocketBool and first_item.is_panel_toggle:
|
||||
self.report({'INFO'}, "Panel already has a toggle")
|
||||
return {'CANCELLED'}
|
||||
item = interface.new_socket(active_panel.name, socket_type='NodeSocketBool', in_out='INPUT')
|
||||
item.is_panel_toggle = True
|
||||
interface.move_to_parent(item, active_panel, 0)
|
||||
# Return in this case because we don't want to move the item.
|
||||
return {'FINISHED'}
|
||||
else:
|
||||
return {'CANCELLED'}
|
||||
|
||||
@@ -409,6 +435,110 @@ class NODE_OT_interface_item_remove(NodeInterfaceOperator, Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class NODE_OT_interface_item_make_panel_toggle(NodeInterfaceOperator, Operator):
|
||||
"""Make the active boolean socket a toggle for its parent panel"""
|
||||
bl_idname = "node.interface_item_make_panel_toggle"
|
||||
bl_label = "Make Panel Toggle"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not super().poll(context):
|
||||
return False
|
||||
|
||||
snode = context.space_data
|
||||
tree = snode.edit_tree
|
||||
interface = tree.interface
|
||||
active_item = interface.active
|
||||
if not active_item:
|
||||
return False
|
||||
if type(active_item) is not bpy.types.NodeTreeInterfaceSocketBool:
|
||||
cls.poll_message_set("Only boolean sockets are supported")
|
||||
return False
|
||||
parent_panel = active_item.parent
|
||||
if parent_panel.parent is None:
|
||||
cls.poll_message_set("Socket must be in a panel")
|
||||
return False
|
||||
if len(parent_panel.interface_items) > 0:
|
||||
first_item = parent_panel.interface_items[0]
|
||||
if first_item.is_panel_toggle:
|
||||
cls.poll_message_set("Panel already has a toggle")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
snode = context.space_data
|
||||
tree = snode.edit_tree
|
||||
interface = tree.interface
|
||||
active_item = interface.active
|
||||
|
||||
parent_panel = active_item.parent
|
||||
if not parent_panel:
|
||||
return {'CANCELLED'}
|
||||
|
||||
if not type(active_item) is bpy.types.NodeTreeInterfaceSocketBool:
|
||||
return {'CANCELLED'}
|
||||
|
||||
active_item.is_panel_toggle = True
|
||||
# Use the same name as the panel in the UI for clarity.
|
||||
active_item.name = parent_panel.name
|
||||
|
||||
# Move the socket to the first position.
|
||||
interface.move_to_parent(active_item, parent_panel, 0)
|
||||
# Make the panel active.
|
||||
interface.active = parent_panel
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class NODE_OT_interface_item_unlink_panel_toggle(NodeInterfaceOperator, Operator):
|
||||
"""Make the panel toggle a stand-alone socket"""
|
||||
bl_idname = "node.interface_item_unlink_panel_toggle"
|
||||
bl_label = "Unlink Panel Toggle"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not super().poll(context):
|
||||
return False
|
||||
|
||||
snode = context.space_data
|
||||
tree = snode.edit_tree
|
||||
interface = tree.interface
|
||||
active_item = interface.active
|
||||
if not active_item or active_item.item_type != 'PANEL':
|
||||
return False
|
||||
if len(active_item.interface_items) == 0:
|
||||
return False
|
||||
|
||||
first_item = active_item.interface_items[0]
|
||||
return first_item.is_panel_toggle
|
||||
|
||||
def execute(self, context):
|
||||
snode = context.space_data
|
||||
tree = snode.edit_tree
|
||||
interface = tree.interface
|
||||
active_item = interface.active
|
||||
|
||||
if not active_item or active_item.item_type != 'PANEL':
|
||||
return {'CANCELLED'}
|
||||
|
||||
if len(active_item.interface_items) == 0:
|
||||
return {'CANCELLED'}
|
||||
|
||||
first_item = active_item.interface_items[0]
|
||||
if not type(first_item) is bpy.types.NodeTreeInterfaceSocketBool or not first_item.is_panel_toggle:
|
||||
return {'CANCELLED'}
|
||||
|
||||
first_item.is_panel_toggle = False
|
||||
first_item.name = active_item.name
|
||||
|
||||
# Make the socket active.
|
||||
interface.active = first_item
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class NODE_OT_viewer_shortcut_set(Operator):
|
||||
"""Create a compositor viewer shortcut for the selected node by pressing ctrl+1,2,..9"""
|
||||
bl_idname = "node.viewer_shortcut_set"
|
||||
@@ -551,6 +681,8 @@ classes = (
|
||||
NODE_OT_interface_item_new,
|
||||
NODE_OT_interface_item_duplicate,
|
||||
NODE_OT_interface_item_remove,
|
||||
NODE_OT_interface_item_make_panel_toggle,
|
||||
NODE_OT_interface_item_unlink_panel_toggle,
|
||||
NODE_OT_tree_path_parent,
|
||||
NODE_OT_viewer_shortcut_get,
|
||||
NODE_OT_viewer_shortcut_set,
|
||||
|
||||
@@ -904,10 +904,18 @@ class NODE_PT_overlay(Panel):
|
||||
class NODE_MT_node_tree_interface_context_menu(Menu):
|
||||
bl_label = "Node Tree Interface Specials"
|
||||
|
||||
def draw(self, _context):
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
snode = context.space_data
|
||||
tree = snode.edit_tree
|
||||
active_item = tree.interface.active
|
||||
|
||||
layout.operator("node.interface_item_duplicate", icon='DUPLICATE')
|
||||
layout.separator()
|
||||
if active_item.item_type == 'SOCKET':
|
||||
layout.operator("node.interface_item_make_panel_toggle")
|
||||
elif active_item.item_type == 'PANEL':
|
||||
layout.operator("node.interface_item_unlink_panel_toggle")
|
||||
|
||||
|
||||
class NODE_PT_node_tree_interface(Panel):
|
||||
@@ -978,6 +986,47 @@ class NODE_PT_node_tree_interface(Panel):
|
||||
layout.use_property_split = False
|
||||
|
||||
|
||||
class NODE_PT_node_tree_interface_panel_toggle(Panel):
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "Group"
|
||||
bl_parent_id = "NODE_PT_node_tree_interface"
|
||||
bl_label = "Panel Toggle"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
snode = context.space_data
|
||||
if snode is None:
|
||||
return False
|
||||
tree = snode.edit_tree
|
||||
if tree is None:
|
||||
return False
|
||||
active_item = tree.interface.active
|
||||
if not active_item or active_item.item_type != 'PANEL':
|
||||
return False
|
||||
if not active_item.interface_items:
|
||||
return False
|
||||
first_item = active_item.interface_items[0]
|
||||
return first_item.is_panel_toggle
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
snode = context.space_data
|
||||
tree = snode.edit_tree
|
||||
|
||||
active_item = tree.interface.active
|
||||
panel_toggle_item = active_item.interface_items[0]
|
||||
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
layout.prop(panel_toggle_item, "default_value", text="Default")
|
||||
layout.prop(panel_toggle_item, "hide_in_modifier")
|
||||
layout.prop(panel_toggle_item, "force_non_field")
|
||||
|
||||
layout.use_property_split = False
|
||||
|
||||
|
||||
class NODE_PT_node_tree_properties(Panel):
|
||||
bl_space_type = 'NODE_EDITOR'
|
||||
bl_region_type = 'UI'
|
||||
@@ -1081,6 +1130,7 @@ classes = (
|
||||
NODE_PT_node_tree_properties,
|
||||
NODE_MT_node_tree_interface_context_menu,
|
||||
NODE_PT_node_tree_interface,
|
||||
NODE_PT_node_tree_interface_panel_toggle,
|
||||
NODE_PT_active_node_generic,
|
||||
NODE_PT_active_node_color,
|
||||
NODE_PT_texture_mapping,
|
||||
|
||||
@@ -264,6 +264,8 @@ class bNodePanelRuntime : NonCopyable, NonMovable {
|
||||
* #bNode::runtime::draw_bounds). */
|
||||
std::optional<float> header_center_y;
|
||||
std::optional<bNodePanelExtent> content_extent;
|
||||
/** Optional socket that is part of the panel header. */
|
||||
bNodeSocket *input_socket = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1036,6 +1036,33 @@ void bNodeTreeInterfacePanel::foreach_item(
|
||||
}
|
||||
}
|
||||
|
||||
const bNodeTreeInterfaceSocket *bNodeTreeInterfacePanel::header_toggle_socket() const
|
||||
{
|
||||
if (this->items().is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
const bNodeTreeInterfaceItem *first_item = this->items().first();
|
||||
if (first_item->item_type != NODE_INTERFACE_SOCKET) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto &socket = *reinterpret_cast<const bNodeTreeInterfaceSocket *>(first_item);
|
||||
if (!(socket.flag & NODE_INTERFACE_SOCKET_INPUT) ||
|
||||
!(socket.flag & NODE_INTERFACE_SOCKET_PANEL_TOGGLE))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const blender::bke::bNodeSocketType *typeinfo = socket.socket_typeinfo();
|
||||
if (!typeinfo || typeinfo->type != SOCK_BOOLEAN) {
|
||||
return nullptr;
|
||||
}
|
||||
return &socket;
|
||||
}
|
||||
bNodeTreeInterfaceSocket *bNodeTreeInterfacePanel::header_toggle_socket()
|
||||
{
|
||||
return const_cast<bNodeTreeInterfaceSocket *>(
|
||||
const_cast<const bNodeTreeInterfacePanel *>(this)->header_toggle_socket());
|
||||
}
|
||||
|
||||
namespace blender::bke::node_interface {
|
||||
|
||||
static bNodeTreeInterfaceSocket *make_socket(const int uid,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "BLI_rand.hh"
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_stack.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8_symbols.h"
|
||||
#include "BLI_vector_set.hh"
|
||||
|
||||
@@ -488,6 +489,10 @@ class NodeTreeMainUpdater {
|
||||
|
||||
ntree.runtime->link_errors_by_target_node.clear();
|
||||
|
||||
if (this->update_panel_toggle_names(ntree)) {
|
||||
result.interface_changed = true;
|
||||
}
|
||||
|
||||
this->update_socket_link_and_use(ntree);
|
||||
this->update_individual_nodes(ntree);
|
||||
this->update_internal_links(ntree);
|
||||
@@ -1711,6 +1716,29 @@ class NodeTreeMainUpdater {
|
||||
|
||||
ntree.tree_interface.reset_changed_flags();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the panel toggle sockets to use the same name as the panel.
|
||||
*/
|
||||
bool update_panel_toggle_names(bNodeTree &ntree)
|
||||
{
|
||||
bool changed = false;
|
||||
ntree.ensure_interface_cache();
|
||||
for (bNodeTreeInterfaceItem *item : ntree.interface_items()) {
|
||||
if (item->item_type != NODE_INTERFACE_PANEL) {
|
||||
continue;
|
||||
}
|
||||
bNodeTreeInterfacePanel *panel = reinterpret_cast<bNodeTreeInterfacePanel *>(item);
|
||||
if (bNodeTreeInterfaceSocket *toggle_socket = panel->header_toggle_socket()) {
|
||||
if (!STREQ(panel->name, toggle_socket->name)) {
|
||||
MEM_SAFE_FREE(toggle_socket->name);
|
||||
toggle_socket->name = BLI_strdup_null(panel->name);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -2409,6 +2409,13 @@ PanelLayout uiLayoutPanelProp(const bContext *C,
|
||||
uiLayout *layout,
|
||||
PointerRNA *open_prop_owner,
|
||||
const char *open_prop_name);
|
||||
PanelLayout uiLayoutPanelPropWithBoolHeader(const bContext *C,
|
||||
uiLayout *layout,
|
||||
PointerRNA *open_prop_owner,
|
||||
const blender::StringRefNull open_prop_name,
|
||||
PointerRNA *bool_prop_owner,
|
||||
const blender::StringRefNull bool_prop_name,
|
||||
const std::optional<blender::StringRefNull> label);
|
||||
|
||||
/**
|
||||
* Variant of #uiLayoutPanelProp that automatically creates the header row with the
|
||||
|
||||
@@ -5008,6 +5008,23 @@ PanelLayout uiLayoutPanelProp(const bContext *C,
|
||||
return panel_layout;
|
||||
}
|
||||
|
||||
PanelLayout uiLayoutPanelPropWithBoolHeader(const bContext *C,
|
||||
uiLayout *layout,
|
||||
PointerRNA *open_prop_owner,
|
||||
const StringRefNull open_prop_name,
|
||||
PointerRNA *bool_prop_owner,
|
||||
const StringRefNull bool_prop_name,
|
||||
const std::optional<StringRefNull> label)
|
||||
{
|
||||
PanelLayout panel = uiLayoutPanelProp(C, layout, open_prop_owner, open_prop_name.c_str());
|
||||
|
||||
uiLayout *panel_header = panel.header;
|
||||
panel_header->flag &= ~(UI_ITEM_PROP_SEP | UI_ITEM_PROP_DECORATE | UI_ITEM_INSIDE_PROP_SEP);
|
||||
uiItemR(panel_header, bool_prop_owner, bool_prop_name, UI_ITEM_NONE, label, ICON_NONE);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
uiLayout *uiLayoutPanelProp(const bContext *C,
|
||||
uiLayout *layout,
|
||||
PointerRNA *open_prop_owner,
|
||||
|
||||
@@ -173,6 +173,7 @@ class NodePanelViewItem : public BasicTreeViewItem {
|
||||
private:
|
||||
bNodeTree &nodetree_;
|
||||
bNodeTreeInterfacePanel &panel_;
|
||||
const bNodeTreeInterfaceSocket *toggle_ = nullptr;
|
||||
|
||||
public:
|
||||
NodePanelViewItem(bNodeTree &nodetree,
|
||||
@@ -185,11 +186,24 @@ class NodePanelViewItem : public BasicTreeViewItem {
|
||||
NodePanelViewItem &self = static_cast<NodePanelViewItem &>(new_active);
|
||||
interface.active_item_set(&self.panel_.item);
|
||||
});
|
||||
toggle_ = panel.header_toggle_socket();
|
||||
is_always_collapsible_ = true;
|
||||
}
|
||||
|
||||
void build_row(uiLayout &row) override
|
||||
{
|
||||
uiLayout *toggle_layout = uiLayoutRow(&row, true);
|
||||
/* Add boolean socket if panel has a toggle. */
|
||||
if (toggle_ != nullptr) {
|
||||
/* XXX Socket template only draws in embossed layouts (Julian). */
|
||||
uiLayoutSetEmboss(toggle_layout, UI_EMBOSS);
|
||||
/* Context is not used by the template function. */
|
||||
uiTemplateNodeSocket(toggle_layout, /*C*/ nullptr, toggle_->socket_color());
|
||||
}
|
||||
else {
|
||||
uiItemL(toggle_layout, "", ICON_BLANK1);
|
||||
}
|
||||
|
||||
this->add_label(row);
|
||||
|
||||
uiLayout *sub = uiLayoutRow(&row, true);
|
||||
@@ -213,11 +227,11 @@ class NodePanelViewItem : public BasicTreeViewItem {
|
||||
}
|
||||
bool rename(const bContext &C, StringRefNull new_name) override
|
||||
{
|
||||
MEM_SAFE_FREE(panel_.name);
|
||||
|
||||
panel_.name = BLI_strdup(new_name.c_str());
|
||||
nodetree_.tree_interface.tag_items_changed();
|
||||
BKE_main_ensure_invariants(*CTX_data_main(&C), nodetree_.id);
|
||||
PointerRNA panel_ptr = RNA_pointer_create_discrete(
|
||||
&nodetree_.id, &RNA_NodeTreeInterfacePanel, &panel_);
|
||||
PropertyRNA *name_prop = RNA_struct_find_property(&panel_ptr, "name");
|
||||
RNA_property_string_set(&panel_ptr, name_prop, new_name.c_str());
|
||||
RNA_property_update(const_cast<bContext *>(&C), &panel_ptr, name_prop);
|
||||
return true;
|
||||
}
|
||||
StringRef get_rename_string() const override
|
||||
@@ -258,9 +272,13 @@ class NodeTreeInterfaceView : public AbstractTreeView {
|
||||
|
||||
protected:
|
||||
void add_items_for_panel_recursive(bNodeTreeInterfacePanel &parent,
|
||||
ui::TreeViewOrItem &parent_item)
|
||||
ui::TreeViewOrItem &parent_item,
|
||||
const bNodeTreeInterfaceItem *skip_item = nullptr)
|
||||
{
|
||||
for (bNodeTreeInterfaceItem *item : parent.items()) {
|
||||
if (item == skip_item) {
|
||||
continue;
|
||||
}
|
||||
switch (item->item_type) {
|
||||
case NODE_INTERFACE_SOCKET: {
|
||||
bNodeTreeInterfaceSocket *socket = node_interface::get_item_as<bNodeTreeInterfaceSocket>(
|
||||
@@ -276,7 +294,10 @@ class NodeTreeInterfaceView : public AbstractTreeView {
|
||||
NodePanelViewItem &panel_item = parent_item.add_tree_item<NodePanelViewItem>(
|
||||
nodetree_, interface_, *panel);
|
||||
panel_item.uncollapse_by_default();
|
||||
add_items_for_panel_recursive(*panel, panel_item);
|
||||
/* Skip over sockets which are a panel toggle. */
|
||||
const bNodeTreeInterfaceSocket *skip_item = panel->header_toggle_socket();
|
||||
add_items_for_panel_recursive(
|
||||
*panel, panel_item, reinterpret_cast<const bNodeTreeInterfaceItem *>(skip_item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -460,7 +481,8 @@ bool NodePanelDropTarget::on_drop(bContext *C, const DragInfo &drag_info) const
|
||||
case DropLocation::Into: {
|
||||
/* Insert into target */
|
||||
parent = &panel_;
|
||||
index = 0;
|
||||
const bool has_toggle_socket = panel_.header_toggle_socket() != nullptr;
|
||||
index = has_toggle_socket ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
case DropLocation::Before: {
|
||||
|
||||
@@ -586,6 +586,8 @@ struct Separator {
|
||||
struct PanelHeader {
|
||||
static constexpr Type type = Type::PanelHeader;
|
||||
const nodes::PanelDeclaration *decl;
|
||||
/** Optional input that is drawn in the header. */
|
||||
bNodeSocket *input = nullptr;
|
||||
};
|
||||
struct PanelContentBegin {
|
||||
static constexpr Type type = Type::PanelContentBegin;
|
||||
@@ -747,7 +749,14 @@ static void add_flat_items_for_panel(bNode &node,
|
||||
if (!panel_visibility[panel_decl.index]) {
|
||||
return;
|
||||
}
|
||||
r_items.append({flat_item::PanelHeader{&panel_decl}});
|
||||
flat_item::PanelHeader header_item;
|
||||
header_item.decl = &panel_decl;
|
||||
const nodes::SocketDeclaration *panel_input_decl = panel_decl.panel_input_decl();
|
||||
if (panel_input_decl) {
|
||||
header_item.input = &node.socket_by_decl(*panel_input_decl);
|
||||
}
|
||||
r_items.append({header_item});
|
||||
|
||||
const bNodePanelState &panel_state = node.panel_states_array[panel_decl.index];
|
||||
if (panel_state.is_collapsed()) {
|
||||
return;
|
||||
@@ -755,6 +764,9 @@ static void add_flat_items_for_panel(bNode &node,
|
||||
r_items.append({flat_item::PanelContentBegin{&panel_decl}});
|
||||
const nodes::SocketDeclaration *prev_socket_decl = nullptr;
|
||||
for (const nodes::ItemDeclaration *item_decl : panel_decl.items) {
|
||||
if (item_decl == panel_input_decl) {
|
||||
continue;
|
||||
}
|
||||
if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
|
||||
add_flat_items_for_socket(node, *socket_decl, &panel_decl, prev_socket_decl, r_items);
|
||||
prev_socket_decl = socket_decl;
|
||||
@@ -1071,6 +1083,7 @@ static void node_update_basis_from_declaration(
|
||||
for (bke::bNodePanelRuntime &panel_runtime : node.runtime->panels) {
|
||||
panel_runtime.header_center_y.reset();
|
||||
panel_runtime.content_extent.reset();
|
||||
panel_runtime.input_socket = nullptr;
|
||||
}
|
||||
for (bNodeSocket *socket : node.input_sockets()) {
|
||||
socket->flag &= ~SOCK_PANEL_COLLAPSED;
|
||||
@@ -1155,6 +1168,11 @@ static void node_update_basis_from_declaration(
|
||||
locy -= panel_header_height / 2;
|
||||
panel_runtime.header_center_y = locy;
|
||||
locy -= panel_header_height / 2;
|
||||
bNodeSocket *input_socket = item.input;
|
||||
if (input_socket) {
|
||||
panel_runtime.input_socket = input_socket;
|
||||
input_socket->runtime->location = float2(locx, *panel_runtime.header_center_y);
|
||||
}
|
||||
}
|
||||
else if constexpr (std::is_same_v<ItemT, flat_item::PanelContentBegin>) {
|
||||
const nodes::PanelDeclaration &node_decl = *item.decl;
|
||||
@@ -2487,6 +2505,7 @@ static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block
|
||||
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];
|
||||
bNodeSocket *input_socket = panel_runtime.input_socket;
|
||||
const bNodePanelState &panel_state = node.panel_states_array[panel_i];
|
||||
if (!panel_runtime.header_center_y.has_value()) {
|
||||
continue;
|
||||
@@ -2498,41 +2517,6 @@ static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block
|
||||
*panel_runtime.header_center_y + NODE_DYS};
|
||||
UI_block_emboss_set(&block, UI_EMBOSS_NONE);
|
||||
|
||||
/* Collapse/expand icon. */
|
||||
const int but_size = U.widget_unit * 0.8f;
|
||||
uiDefIconBut(&block,
|
||||
UI_BTYPE_BUT_TOGGLE,
|
||||
0,
|
||||
panel_state.is_collapsed() ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT,
|
||||
draw_bounds.xmin + (NODE_MARGIN_X / 3),
|
||||
*panel_runtime.header_center_y - but_size / 2,
|
||||
but_size,
|
||||
but_size,
|
||||
nullptr,
|
||||
0.0f,
|
||||
0.0f,
|
||||
"");
|
||||
|
||||
/* Panel label. */
|
||||
uiBut *label_but = uiDefBut(
|
||||
&block,
|
||||
UI_BTYPE_LABEL,
|
||||
0,
|
||||
IFACE_(panel_decl.name),
|
||||
int(draw_bounds.xmin + NODE_MARGIN_X + 0.4f),
|
||||
int(*panel_runtime.header_center_y - NODE_DYS),
|
||||
short(draw_bounds.xmax - draw_bounds.xmin - (30.0f * UI_SCALE_FAC)),
|
||||
NODE_DY,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
"");
|
||||
|
||||
const bool only_inactive_inputs = panel_has_only_inactive_inputs(node, panel_decl);
|
||||
if (node.is_muted() || only_inactive_inputs) {
|
||||
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;
|
||||
uiBut *toggle_action_but = uiDefIconBut(
|
||||
@@ -2555,7 +2539,66 @@ static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block
|
||||
const_cast<bNodePanelState *>(&panel_state),
|
||||
&ntree);
|
||||
|
||||
/* Collapse/expand icon. */
|
||||
const int but_size = U.widget_unit * 0.8f;
|
||||
const int but_padding = NODE_MARGIN_X / 4;
|
||||
int offsetx = draw_bounds.xmin + (NODE_MARGIN_X / 3);
|
||||
uiDefIconBut(&block,
|
||||
UI_BTYPE_LABEL,
|
||||
0,
|
||||
panel_state.is_collapsed() ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT,
|
||||
offsetx,
|
||||
*panel_runtime.header_center_y - but_size / 2,
|
||||
but_size,
|
||||
but_size,
|
||||
nullptr,
|
||||
0.0f,
|
||||
0.0f,
|
||||
"");
|
||||
offsetx += but_size + but_padding;
|
||||
|
||||
UI_block_emboss_set(&block, UI_EMBOSS);
|
||||
|
||||
/* Panel toggle. */
|
||||
if (input_socket && !input_socket->is_logically_linked()) {
|
||||
PointerRNA socket_ptr = RNA_pointer_create_discrete(
|
||||
&ntree.id, &RNA_NodeSocket, input_socket);
|
||||
uiDefButR(&block,
|
||||
UI_BTYPE_CHECKBOX,
|
||||
-1,
|
||||
"",
|
||||
offsetx,
|
||||
int(*panel_runtime.header_center_y - NODE_DYS),
|
||||
UI_UNIT_X,
|
||||
NODE_DY,
|
||||
&socket_ptr,
|
||||
"default_value",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
"");
|
||||
offsetx += UI_UNIT_X;
|
||||
}
|
||||
|
||||
/* Panel label. */
|
||||
uiBut *label_but = uiDefBut(
|
||||
&block,
|
||||
UI_BTYPE_LABEL,
|
||||
0,
|
||||
IFACE_(panel_decl.name),
|
||||
offsetx,
|
||||
int(*panel_runtime.header_center_y - NODE_DYS),
|
||||
short(draw_bounds.xmax - draw_bounds.xmin - (30.0f * UI_SCALE_FAC)),
|
||||
NODE_DY,
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
"");
|
||||
|
||||
const bool only_inactive_inputs = panel_has_only_inactive_inputs(node, panel_decl);
|
||||
if (node.is_muted() || only_inactive_inputs) {
|
||||
UI_but_flag_enable(label_but, UI_BUT_INACTIVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,8 +65,10 @@ typedef enum NodeTreeInterfaceSocketFlag {
|
||||
NODE_INTERFACE_SOCKET_LAYER_SELECTION = 1 << 6,
|
||||
/* INSPECT is used by Connect to Output operator to ensure socket that exits from node group. */
|
||||
NODE_INTERFACE_SOCKET_INSPECT = 1 << 7,
|
||||
/* Socket is used in the panel header as a toggle. */
|
||||
NODE_INTERFACE_SOCKET_PANEL_TOGGLE = 1 << 8,
|
||||
} NodeTreeInterfaceSocketFlag;
|
||||
ENUM_OPERATORS(NodeTreeInterfaceSocketFlag, NODE_INTERFACE_SOCKET_INSPECT);
|
||||
ENUM_OPERATORS(NodeTreeInterfaceSocketFlag, NODE_INTERFACE_SOCKET_PANEL_TOGGLE);
|
||||
|
||||
typedef struct bNodeTreeInterfaceSocket {
|
||||
bNodeTreeInterfaceItem item;
|
||||
@@ -222,6 +224,10 @@ typedef struct bNodeTreeInterfacePanel {
|
||||
void foreach_item(blender::FunctionRef<bool(const bNodeTreeInterfaceItem &item)> fn,
|
||||
bool include_self = false) const;
|
||||
|
||||
/** Get the socket that is part of the panel header if available. */
|
||||
const bNodeTreeInterfaceSocket *header_toggle_socket() const;
|
||||
bNodeTreeInterfaceSocket *header_toggle_socket();
|
||||
|
||||
private:
|
||||
/** Find a valid position for inserting in the items span. */
|
||||
int find_valid_insert_position_for_item(const bNodeTreeInterfaceItem &item,
|
||||
|
||||
@@ -1034,6 +1034,14 @@ static void rna_def_node_interface_socket(BlenderRNA *brna)
|
||||
"Take link out of node group to connect to root tree output node");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
|
||||
|
||||
prop = RNA_def_property(srna, "is_panel_toggle", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "flag", NODE_INTERFACE_SOCKET_PANEL_TOGGLE);
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
RNA_def_property_ui_text(prop,
|
||||
"Is Panel Toggle",
|
||||
"This socket is meant to be used as the toggle in its panel header");
|
||||
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeTreeInterfaceItem_update");
|
||||
|
||||
prop = RNA_def_property(srna, "layer_selection_field", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_boolean_sdna(prop, nullptr, "flag", NODE_INTERFACE_SOCKET_LAYER_SELECTION);
|
||||
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
|
||||
|
||||
@@ -2275,9 +2275,11 @@ static bool interface_panel_affects_output(DrawGroupInputsContext &ctx,
|
||||
|
||||
static void draw_interface_panel_content(DrawGroupInputsContext &ctx,
|
||||
uiLayout *layout,
|
||||
const bNodeTreeInterfacePanel &interface_panel)
|
||||
const bNodeTreeInterfacePanel &interface_panel,
|
||||
const bool skip_first = false)
|
||||
{
|
||||
for (const bNodeTreeInterfaceItem *item : interface_panel.items()) {
|
||||
for (const bNodeTreeInterfaceItem *item : interface_panel.items().drop_front(skip_first ? 1 : 0))
|
||||
{
|
||||
if (item->item_type == NODE_INTERFACE_PANEL) {
|
||||
const auto &sub_interface_panel = *reinterpret_cast<const bNodeTreeInterfacePanel *>(item);
|
||||
if (!interface_panel_has_socket(sub_interface_panel)) {
|
||||
@@ -2286,8 +2288,39 @@ static void draw_interface_panel_content(DrawGroupInputsContext &ctx,
|
||||
NodesModifierPanel *panel = find_panel_by_id(ctx.nmd, sub_interface_panel.identifier);
|
||||
PointerRNA panel_ptr = RNA_pointer_create_discrete(
|
||||
ctx.md_ptr->owner_id, &RNA_NodesModifierPanel, panel);
|
||||
PanelLayout panel_layout = uiLayoutPanelProp(&ctx.C, layout, &panel_ptr, "is_open");
|
||||
uiItemL(panel_layout.header, IFACE_(sub_interface_panel.name), ICON_NONE);
|
||||
PanelLayout panel_layout;
|
||||
bool skip_first = false;
|
||||
/* Check if the panel should have a toggle in the header. */
|
||||
const bNodeTreeInterfaceSocket *toggle_socket = sub_interface_panel.header_toggle_socket();
|
||||
if (toggle_socket && !(toggle_socket->flag & NODE_INTERFACE_SOCKET_HIDE_IN_MODIFIER)) {
|
||||
const StringRefNull identifier = toggle_socket->identifier;
|
||||
IDProperty *property = IDP_GetPropertyFromGroup(ctx.nmd.settings.properties, identifier);
|
||||
/* IDProperties can be removed with python, so there could be a situation where
|
||||
* there isn't a property for a socket or it doesn't have the correct type. */
|
||||
if (property == nullptr ||
|
||||
!nodes::id_property_type_matches_socket(*toggle_socket, *property))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
char socket_id_esc[MAX_NAME * 2];
|
||||
BLI_str_escape(socket_id_esc, identifier.c_str(), sizeof(socket_id_esc));
|
||||
|
||||
char rna_path[sizeof(socket_id_esc) + 4];
|
||||
SNPRINTF(rna_path, "[\"%s\"]", socket_id_esc);
|
||||
|
||||
panel_layout = uiLayoutPanelPropWithBoolHeader(&ctx.C,
|
||||
layout,
|
||||
&panel_ptr,
|
||||
"is_open",
|
||||
ctx.md_ptr,
|
||||
rna_path,
|
||||
IFACE_(sub_interface_panel.name));
|
||||
skip_first = true;
|
||||
}
|
||||
else {
|
||||
panel_layout = uiLayoutPanelProp(&ctx.C, layout, &panel_ptr, "is_open");
|
||||
uiItemL(panel_layout.header, IFACE_(sub_interface_panel.name), ICON_NONE);
|
||||
}
|
||||
if (!interface_panel_affects_output(ctx, sub_interface_panel)) {
|
||||
uiLayoutSetActive(panel_layout.header, false);
|
||||
}
|
||||
@@ -2301,7 +2334,7 @@ static void draw_interface_panel_content(DrawGroupInputsContext &ctx,
|
||||
nullptr,
|
||||
nullptr);
|
||||
if (panel_layout.body) {
|
||||
draw_interface_panel_content(ctx, panel_layout.body, sub_interface_panel);
|
||||
draw_interface_panel_content(ctx, panel_layout.body, sub_interface_panel, skip_first);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -189,6 +189,8 @@ class SocketDeclaration : public ItemDeclaration {
|
||||
bool is_default_link_socket = false;
|
||||
/** Puts this socket on the same line as the previous one in the UI. */
|
||||
bool align_with_previous_socket = false;
|
||||
/** This socket is used as a toggle for the parent panel. */
|
||||
bool is_panel_toggle = false;
|
||||
|
||||
/** Index in the list of inputs or outputs of the node. */
|
||||
int index = -1;
|
||||
@@ -388,6 +390,10 @@ class BaseSocketDeclarationBuilder {
|
||||
const StructRNA *srna,
|
||||
const void *data,
|
||||
StringRef property_name);
|
||||
/**
|
||||
* Use the socket as a toggle in its panel.
|
||||
*/
|
||||
BaseSocketDeclarationBuilder &panel_toggle(bool value = true);
|
||||
|
||||
/** Index in the list of inputs or outputs. */
|
||||
int index() const;
|
||||
@@ -455,6 +461,9 @@ class PanelDeclaration : public ItemDeclaration {
|
||||
void update_or_build(const bNodePanelState &old_panel, bNodePanelState &new_panel) const;
|
||||
|
||||
int depth() const;
|
||||
|
||||
/** Get the declaration for a child item that should be drawn as part of the panel header. */
|
||||
const SocketDeclaration *panel_input_decl() const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -372,6 +372,7 @@ static BaseSocketDeclarationBuilder &build_interface_socket_declaration(
|
||||
decl->description(io_socket.description ? io_socket.description : "");
|
||||
decl->hide_value(io_socket.flag & NODE_INTERFACE_SOCKET_HIDE_VALUE);
|
||||
decl->compact(io_socket.flag & NODE_INTERFACE_SOCKET_COMPACT);
|
||||
decl->panel_toggle(io_socket.flag & NODE_INTERFACE_SOCKET_PANEL_TOGGLE);
|
||||
return *decl;
|
||||
}
|
||||
|
||||
|
||||
@@ -495,6 +495,22 @@ int PanelDeclaration::depth() const
|
||||
return count;
|
||||
}
|
||||
|
||||
const nodes::SocketDeclaration *PanelDeclaration::panel_input_decl() const
|
||||
{
|
||||
if (this->items.is_empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
const nodes::ItemDeclaration *item_decl = this->items.first();
|
||||
if (const auto *socket_decl = dynamic_cast<const nodes::SocketDeclaration *>(item_decl)) {
|
||||
if (socket_decl->is_panel_toggle && (socket_decl->in_out & SOCK_IN) &&
|
||||
(socket_decl->socket_type & SOCK_BOOLEAN))
|
||||
{
|
||||
return socket_decl;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::supports_field()
|
||||
{
|
||||
BLI_assert(this->is_input());
|
||||
@@ -743,6 +759,12 @@ BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::socket_name_ptr(
|
||||
property_name);
|
||||
}
|
||||
|
||||
BaseSocketDeclarationBuilder &BaseSocketDeclarationBuilder::panel_toggle(const bool value)
|
||||
{
|
||||
decl_base_->is_panel_toggle = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
OutputFieldDependency OutputFieldDependency::ForFieldSource()
|
||||
{
|
||||
OutputFieldDependency field_dependency;
|
||||
|
||||
Reference in New Issue
Block a user