Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions NodeGraphQt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
into applications that supports **PySide2**.

project: https://github.com/jchanvfx/NodeGraphQt
documantation: https://jchanvfx.github.io/NodeGraphQt/api/html/index.html
documentation: https://jchanvfx.github.io/NodeGraphQt/api/html/index.html

example code:

Expand Down Expand Up @@ -87,7 +87,8 @@ def __init__(self):
update_nodes_by_up, update_nodes_by_down

# widgets
from .widgets.node_tree import NodeTreeWidget
from .widgets.nodes_tree import NodeTreeWidget
from .widgets.nodes_palette import NodesPaletteWidget
from .widgets.properties_bin import PropertiesBinWidget
from .widgets.node_publish_widget import NodePublishWidget
from .widgets.node_widgets import NodeBaseWidget
Expand All @@ -103,6 +104,7 @@ def __init__(self):
'NodeGraphCommand',
'NodeGraphMenu',
'NodeObject',
'NodesPaletteWidget',
'NodeTreeWidget',
'NodesMenu',
'Port',
Expand Down
252 changes: 252 additions & 0 deletions NodeGraphQt/widgets/nodes_palette.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
from collections import defaultdict

from Qt import QtWidgets, QtCore, QtGui

from ..constants import URN_SCHEME


class NodesGridDelagate(QtWidgets.QStyledItemDelegate):

def paint(self, painter, option, index):
"""
Args:
painter (QtGui.QPainter):
option (QtGui.QStyleOptionViewItem):
index (QtCore.QModelIndex):
"""
if index.column() != 0:
super(NodesGridDelagate, self).paint(painter, option, index)
return

model = index.model().sourceModel()
item = model.item(index.row(), index.column())

sub_margin = 2
radius = 5

base_rect = QtCore.QRectF(
option.rect.x() + sub_margin,
option.rect.y() + sub_margin,
option.rect.width() - (sub_margin * 2),
option.rect.height() - (sub_margin * 2)
)

painter.save()
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)

# background.
bg_color = option.palette.window().color()
pen_color = option.palette.midlight().color().lighter(120)
if option.state & QtWidgets.QStyle.State_Selected:
bg_color = bg_color.lighter(120)
pen_color = pen_color.lighter(160)

pen = QtGui.QPen(pen_color, 3.0)
pen.setCapStyle(QtCore.Qt.RoundCap)
painter.setPen(pen)
painter.setBrush(QtGui.QBrush(bg_color))
painter.drawRoundRect(base_rect,
int(base_rect.height()/radius),
int(base_rect.width()/radius))

pen_color = option.palette.midlight().color().darker(130)
if option.state & QtWidgets.QStyle.State_Selected:
pen_color = option.palette.highlight().color()
pen = QtGui.QPen(pen_color, 1.0)
pen.setCapStyle(QtCore.Qt.RoundCap)
painter.setPen(pen)
painter.setBrush(QtCore.Qt.NoBrush)

sub_margin = 6
sub_rect = QtCore.QRectF(
base_rect.x() + sub_margin,
base_rect.y() + sub_margin,
base_rect.width() - (sub_margin * 2),
base_rect.height() - (sub_margin * 2)
)
painter.drawRoundRect(sub_rect,
int(sub_rect.height() / radius),
int(sub_rect.width() / radius))

painter.setBrush(QtGui.QBrush(pen_color))
edge_size = 2, sub_rect.height() - 6
left_x = sub_rect.left()
right_x = sub_rect.right() - edge_size[0]
pos_y = sub_rect.center().y() - (edge_size[1] / 2)

for pos_x in [left_x, right_x]:
painter.drawRect(QtCore.QRectF(
pos_x, pos_y, edge_size[0], edge_size[1]
))

# painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(QtGui.QBrush(bg_color))
dot_size = 4
left_x = sub_rect.left() - 1
right_x = sub_rect.right() - (dot_size - 1)
pos_y = sub_rect.center().y() - (dot_size / 2)
for pos_x in [left_x, right_x]:
painter.drawEllipse(QtCore.QRectF(
pos_x, pos_y, dot_size, dot_size
))
pos_x -= dot_size + 2

# text
pen_color = option.palette.text().color()
pen = QtGui.QPen(pen_color, 0.5)
pen.setCapStyle(QtCore.Qt.RoundCap)
painter.setPen(pen)

font = painter.font()
font_metrics = QtGui.QFontMetrics(font)
font_width = font_metrics.width(item.text().replace(' ', '_'))
font_height = font_metrics.height()
text_rect = QtCore.QRectF(
sub_rect.center().x() - (font_width / 2),
sub_rect.center().y() - (font_height * 0.55),
font_width, font_height)
painter.drawText(text_rect, item.text())
painter.restore()


class NodesGridProxyModel(QtCore.QSortFilterProxyModel):

def __init__(self, parent=None):
super(NodesGridProxyModel, self).__init__(parent)

def mimeData(self, indexes):
node_ids = ['node:{}'.format(i.data(QtCore.Qt.ToolTipRole))
for i in indexes]
node_urn = URN_SCHEME + ';'.join(node_ids)
mime_data = super(NodesGridProxyModel, self).mimeData(indexes)
mime_data.setUrls([node_urn])
return mime_data


class NodesGridView(QtWidgets.QListView):

def __init__(self, parent=None):
super(NodesGridView, self).__init__(parent)
self.setSelectionMode(self.ExtendedSelection)
self.setUniformItemSizes(True)
self.setResizeMode(self.Adjust)
self.setViewMode(self.IconMode)
self.setDragDropMode(self.DragOnly)
self.setDragEnabled(True)
self.setMinimumSize(450, 300)
self.setSpacing(4)

model = QtGui.QStandardItemModel()
proxy_model = NodesGridProxyModel()
proxy_model.setSourceModel(model)
self.setModel(proxy_model)
self.setItemDelegate(NodesGridDelagate(self))

def clear(self):
self.model().sourceMode().clear()

def add_item(self, label, tooltip=''):
item = QtGui.QStandardItem(label)
item.setSizeHint(QtCore.QSize(130, 40))
item.setToolTip(tooltip)
model = self.model().sourceModel()
model.appendRow(item)


class NodesPaletteWidget(QtWidgets.QWidget):

def __init__(self, parent=None, node_graph=None):
super(NodesPaletteWidget, self).__init__(parent)
self.setWindowTitle('Nodes')

self._category_tabs = {}
self._custom_labels = {}
self._factory = node_graph.node_factory if node_graph else None

self._tab_widget = QtWidgets.QTabWidget()
self._tab_widget.setMovable(True)

layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self._tab_widget)

self._build_ui()

def __repr__(self):
return '<{} object at {}>'.format(
self.__class__.__name__, hex(id(self))
)

def _build_ui(self):
"""
populate the ui
"""
categories = set()
node_types = defaultdict(list)
for name, node_ids in self._factory.names.items():
for nid in node_ids:
category = '.'.join(nid.split('.')[:-1])
categories.add(category)
node_types[category].append((nid, name))

for category, nodes_list in node_types.items():
grid_view = self._add_category_tab(category)
for node_id, node_name in nodes_list:
grid_view.add_item(node_name, node_id)

def _set_node_factory(self, factory):
"""
Set current node factory.

Args:
factory (NodeFactory): node factory.
"""
self._factory = factory

def _add_category_tab(self, category):
"""
Adds a new tab to the node palette widget.

Args:
category (str): node identifier category eg. ``"nodes.widgets"``

Returns:
NodesGridView: nodes grid view widget.
"""
if category not in self._category_tabs:
grid_widget = NodesGridView(self)
self._tab_widget.addTab(grid_widget, category)
self._category_tabs[category] = grid_widget
return self._category_tabs[category]

def set_category_label(self, category, label):
"""
Override tab label for a node category tab.

Args:
category (str): node identifier category eg. ``"nodes.widgets"``
label (str): custom display label. eg. ``"Node Widgets"``
"""
if label in self._custom_labels.values():
labels = {v: k for k, v in self._custom_labels.items()}
raise ValueError('label "{}" already in use for "{}"'
.format(label, labels[label]))
previous_label = self._custom_labels.get(category, '')
for idx in range(self._tab_widget.count()):
tab_text = self._tab_widget.tabText(idx)
if tab_text in [category, previous_label]:
self._tab_widget.setTabText(idx, label)
break
self._custom_labels[category] = label

def update(self):
"""
Update and refresh the node palette widget.
"""
self._build_tree()





Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,18 @@ class NodeTreeWidget(QtWidgets.QTreeWidget):
def __init__(self, parent=None, node_graph=None):
super(NodeTreeWidget, self).__init__(parent)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly)
self.setSelectionMode(self.ExtendedSelection)
self.setHeaderHidden(True)
self.setWindowTitle('Nodes')
self._factory = None

self._factory = node_graph.node_factory if node_graph else None
self._custom_labels = {}
self._set_node_factory(node_graph.node_factory)
self._category_items = {}

def __repr__(self):
return '<{} object at {}>'.format(self.__class__.__name__, hex(id(self)))
return '<{} object at {}>'.format(
self.__class__.__name__, hex(id(self))
)

def mimeData(self, items):
node_ids = ['node:{}'.format(i.toolTip(0)) for i in items]
Expand All @@ -59,7 +63,7 @@ def _build_tree(self):
categories.add('.'.join(nid.split('.')[:-1]))
node_types[nid] = name

category_items = {}
self._category_items = {}
for category in sorted(categories):
if category in self._custom_labels.keys():
label = self._custom_labels[category]
Expand All @@ -72,11 +76,11 @@ def _build_tree(self):
cat_item.setSizeHint(0, QtCore.QSize(100, 26))
self.addTopLevelItem(cat_item)
cat_item.setExpanded(True)
category_items[category] = cat_item
self._category_items[category] = cat_item

for node_id, node_name in node_types.items():
category = '.'.join(node_id.split('.')[:-1])
category_item = category_items[category]
category_item = self._category_items[category]

item = BaseNodeTreeItem(category_item, [node_name], type=TYPE_NODE)
item.setToolTip(0, node_id)
Expand All @@ -95,7 +99,7 @@ def _set_node_factory(self, factory):

def set_category_label(self, category, label):
"""
Set custom label for a node category root item.
Override the label for a node category root item.

.. image:: _images/nodes_tree_category_label.png
:width: 70%
Expand All @@ -105,6 +109,9 @@ def set_category_label(self, category, label):
label (str): custom display label. eg. ``"Node Widgets"``
"""
self._custom_labels[category] = label
if category in self._category_items:
item = self._category_items[category]
item.setText(0, label)

def update(self):
"""
Expand Down
Binary file added docs/_images/nodes_palette.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions docs/custom_widgets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,36 @@ example
:members:
:exclude-members: property_changed

Nodes Palette
*************

The :class:`NodeGraphQt.NodesPaletteWidget` is a widget for displaying all
registered nodes from the node graph in a grid layout with this widget a user
can create nodes by dragging and dropping.

.. image:: _images/nodes_palette.png
:width: 400px

example

.. code-block:: python
:linenos:

from NodeGraphQt import NodeGraph, NodesPaletteWidget

# create node graph.
graph = NodeGraph()

# create nodes palette widget.
nodes_palette = NodesPaletteWidget(parent=None, node_graph=graph)
nodes_palette.show()

----

.. autoclass:: NodeGraphQt.NodesPaletteWidget
:members:
:exclude-members: mimeData,

Nodes Tree
**********

Expand Down