185 lines
6.1 KiB
Python
185 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Enhanced Backdrop Node (Inherited from BaseNode)
|
|
|
|
Features:
|
|
- Inherits from `BaseNode` so it can be discovered in your node scanning.
|
|
- Custom context menu to rename (set title) or pick a new color.
|
|
- Forces geometry updates to reduce "ghosting" or partial redraws.
|
|
"""
|
|
|
|
from Qt import QtWidgets, QtGui
|
|
from OdenGraphQt import BaseNode
|
|
from OdenGraphQt.constants import NodePropWidgetEnum
|
|
from OdenGraphQt.qgraphics.node_backdrop import BackdropNodeItem
|
|
|
|
|
|
class BackdropNode(BaseNode):
|
|
"""
|
|
Backdrop Node:
|
|
- Allows grouping or annotating other nodes by resizing a large rectangle.
|
|
- Provides a custom context menu for renaming and recoloring (via on_context_menu).
|
|
"""
|
|
|
|
__identifier__ = 'bunny-lab.io.backdrop'
|
|
NODE_NAME = 'Backdrop'
|
|
|
|
def __init__(self):
|
|
# Use BackdropNodeItem for the specialized QGraphicsItem.
|
|
super(BackdropNode, self).__init__(qgraphics_item=BackdropNodeItem)
|
|
|
|
# Default color (teal).
|
|
self.model.color = (5, 129, 138, 255)
|
|
|
|
# Multi-line text property for storing the backdrop text.
|
|
self.create_property(
|
|
'backdrop_text',
|
|
'',
|
|
widget_type=NodePropWidgetEnum.QTEXT_EDIT.value,
|
|
tab='Backdrop'
|
|
)
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Resizing / Geometry
|
|
# --------------------------------------------------------------------------
|
|
def on_backdrop_updated(self, update_prop, value=None):
|
|
"""
|
|
Triggered when the user resizes or double-clicks the backdrop sizer handle.
|
|
"""
|
|
if not self.graph:
|
|
return
|
|
|
|
if update_prop == 'sizer_mouse_release':
|
|
# User finished dragging the resize handle
|
|
self.view.prepareGeometryChange()
|
|
self.graph.begin_undo(f'resized "{self.name()}"')
|
|
self.set_property('width', value['width'])
|
|
self.set_property('height', value['height'])
|
|
self.set_pos(*value['pos'])
|
|
self.graph.end_undo()
|
|
self.view.update()
|
|
|
|
elif update_prop == 'sizer_double_clicked':
|
|
# User double-clicked the resize handle (auto-resize)
|
|
self.view.prepareGeometryChange()
|
|
self.graph.begin_undo(f'"{self.name()}" auto resize')
|
|
self.set_property('width', value['width'])
|
|
self.set_property('height', value['height'])
|
|
self.set_pos(*value['pos'])
|
|
self.graph.end_undo()
|
|
self.view.update()
|
|
|
|
def auto_size(self):
|
|
"""
|
|
Auto-resize the backdrop to fit around intersecting nodes.
|
|
"""
|
|
if not self.graph:
|
|
return
|
|
self.view.prepareGeometryChange()
|
|
self.graph.begin_undo(f'"{self.name()}" auto resize')
|
|
size = self.view.calc_backdrop_size()
|
|
self.set_property('width', size['width'])
|
|
self.set_property('height', size['height'])
|
|
self.set_pos(*size['pos'])
|
|
self.graph.end_undo()
|
|
self.view.update()
|
|
|
|
def wrap_nodes(self, nodes):
|
|
"""
|
|
Fit the backdrop around the specified nodes.
|
|
"""
|
|
if not self.graph or not nodes:
|
|
return
|
|
self.view.prepareGeometryChange()
|
|
self.graph.begin_undo(f'"{self.name()}" wrap nodes')
|
|
size = self.view.calc_backdrop_size([n.view for n in nodes])
|
|
self.set_property('width', size['width'])
|
|
self.set_property('height', size['height'])
|
|
self.set_pos(*size['pos'])
|
|
self.graph.end_undo()
|
|
self.view.update()
|
|
|
|
def nodes(self):
|
|
"""
|
|
Return a list of nodes wrapped by this backdrop.
|
|
"""
|
|
node_ids = [n.id for n in self.view.get_nodes()]
|
|
return [self.graph.get_node_by_id(nid) for nid in node_ids]
|
|
|
|
def set_text(self, text=''):
|
|
"""
|
|
Set the multi-line text in the backdrop.
|
|
"""
|
|
self.set_property('backdrop_text', text)
|
|
|
|
def text(self):
|
|
"""
|
|
Return the text content in the backdrop.
|
|
"""
|
|
return self.get_property('backdrop_text')
|
|
|
|
def set_size(self, width, height):
|
|
"""
|
|
Manually set the backdrop size.
|
|
"""
|
|
if self.graph:
|
|
self.view.prepareGeometryChange()
|
|
self.graph.begin_undo('backdrop size')
|
|
self.set_property('width', width)
|
|
self.set_property('height', height)
|
|
self.graph.end_undo()
|
|
self.view.update()
|
|
else:
|
|
self.view.width, self.view.height = width, height
|
|
self.model.width, self.model.height = width, height
|
|
|
|
def size(self):
|
|
"""
|
|
Return (width, height) of the backdrop.
|
|
"""
|
|
self.model.width = self.view.width
|
|
self.model.height = self.view.height
|
|
return self.model.width, self.model.height
|
|
|
|
# No ports for a backdrop:
|
|
def inputs(self):
|
|
return
|
|
|
|
def outputs(self):
|
|
return
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Custom Context Menu
|
|
# --------------------------------------------------------------------------
|
|
def on_context_menu(self, menu):
|
|
"""
|
|
Called manually by the node context menu callback in older NodeGraphQt versions.
|
|
"""
|
|
rename_action = menu.addAction("Set Title...")
|
|
rename_action.triggered.connect(self._change_title)
|
|
|
|
color_action = menu.addAction("Set Color...")
|
|
color_action.triggered.connect(self._change_color)
|
|
|
|
def _change_title(self):
|
|
"""
|
|
Prompt for a new backdrop title (header).
|
|
"""
|
|
new_title, ok = QtWidgets.QInputDialog.getText(
|
|
None, "Backdrop Title", "Enter new backdrop title:"
|
|
)
|
|
if ok and new_title:
|
|
self.set_name(new_title)
|
|
|
|
def _change_color(self):
|
|
"""
|
|
Prompt for a new backdrop color via QColorDialog.
|
|
"""
|
|
current_color = QtGui.QColor(*self.model.color)
|
|
color = QtWidgets.QColorDialog.getColor(
|
|
current_color, None, "Select Backdrop Color"
|
|
)
|
|
if color.isValid():
|
|
self.model.color = (color.red(), color.green(), color.blue(), color.alpha())
|
|
self.view.update()
|