Fix: Nodes: bad ui for adding panel toggles

Adding panel toggles in nodegroups have somewhat of a UX antipattern. When
running the operator, it checks for conditions that indicate it should not run,
and if those are hit, it cancels execution and mentions the invalid condition in
the footer bar.

This is not ideal, the user should not have to call the operator to find out
whether it can be called.

Why it got implemented like this is likely a consequence of all interface items
being the same "New Item" operator. Poll functions cannot use operator
properties, so variants of the same operator cannot check for different
conditions for execution.

This is a problem for panel toggles, as they have more restrictions to when they
can be added that don't apply to other interface items.

This patch creates a separate operator for adding panel toggles. This allows the
condition checks to be implemented in the poll function, which enables greying
out the operator buttons and showing on tooltips what condition is invalid.

Pull Request: https://projects.blender.org/blender/blender/pulls/146379
This commit is contained in:
quackarooni
2025-10-16 18:37:22 +02:00
committed by Jacques Lucke
parent a1fde8bed8
commit ce88d773db
3 changed files with 71 additions and 36 deletions

View File

@@ -916,32 +916,15 @@ class NODE_OT_interface_item_new(NodeInterfaceOperator, Operator):
bl_label = "New Item"
bl_options = {'REGISTER', 'UNDO'}
def get_items(_self, context):
items = [
('INPUT', "Input", ""),
('OUTPUT', "Output", ""),
('PANEL', "Panel", ""),
]
if context is None:
return items
snode = context.space_data
tree = snode.edit_tree
interface = tree.interface
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,
items=(
('INPUT', "Input", ""),
('OUTPUT', "Output", ""),
('PANEL', "Panel", ""),
),
default='INPUT',
)
# Returns a valid socket type for the given tree or None.
@@ -977,18 +960,6 @@ 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'}
@@ -1003,6 +974,55 @@ class NODE_OT_interface_item_new(NodeInterfaceOperator, Operator):
return {'FINISHED'}
class NODE_OT_interface_item_new_panel_toggle(Operator):
'''Add a checkbox to the currently selected panel'''
bl_idname = "node.interface_item_new_panel_toggle"
bl_label = "New Panel Toggle"
bl_options = {'REGISTER', 'UNDO'}
@staticmethod
def get_panel_toggle(panel):
if len(panel.interface_items) > 0:
first_item = panel.interface_items[0]
if type(first_item) is bpy.types.NodeTreeInterfaceSocketBool and first_item.is_panel_toggle:
return first_item
return None
@classmethod
def poll(cls, context):
try:
snode = context.space_data
tree = snode.edit_tree
interface = tree.interface
active_item = interface.active
if active_item.item_type != 'PANEL':
cls.poll_message_set("Active item is not a panel")
return False
if cls.get_panel_toggle(active_item) is not None:
cls.poll_message_set("Panel already has a toggle")
return False
return True
except AttributeError:
return False
def execute(self, context):
snode = context.space_data
tree = snode.edit_tree
interface = tree.interface
active_panel = interface.active
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 {'FINISHED'}
class NODE_OT_interface_item_duplicate(NodeInterfaceOperator, Operator):
"""Add a copy of the active item to the interface"""
bl_idname = "node.interface_item_duplicate"
@@ -1309,6 +1329,7 @@ classes = (
NODE_OT_add_closure_zone,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_interface_item_new,
NODE_OT_interface_item_new_panel_toggle,
NODE_OT_interface_item_duplicate,
NODE_OT_interface_item_remove,
NODE_OT_interface_item_make_panel_toggle,