Skip to content

Commit ba90dbd

Browse files
authored
Merge pull request #216 from jchanvfx/nodes_palette_widget
implemented nodes palette widget #40
2 parents dcd2adb + cd56c57 commit ba90dbd

File tree

5 files changed

+300
-9
lines changed

5 files changed

+300
-9
lines changed

NodeGraphQt/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
into applications that supports **PySide2**.
3535
3636
project: https://github.com/jchanvfx/NodeGraphQt
37-
documantation: https://jchanvfx.github.io/NodeGraphQt/api/html/index.html
37+
documentation: https://jchanvfx.github.io/NodeGraphQt/api/html/index.html
3838
3939
example code:
4040
@@ -87,7 +87,8 @@ def __init__(self):
8787
update_nodes_by_up, update_nodes_by_down
8888

8989
# widgets
90-
from .widgets.node_tree import NodeTreeWidget
90+
from .widgets.nodes_tree import NodeTreeWidget
91+
from .widgets.nodes_palette import NodesPaletteWidget
9192
from .widgets.properties_bin import PropertiesBinWidget
9293
from .widgets.node_publish_widget import NodePublishWidget
9394
from .widgets.node_widgets import NodeBaseWidget
@@ -103,6 +104,7 @@ def __init__(self):
103104
'NodeGraphCommand',
104105
'NodeGraphMenu',
105106
'NodeObject',
107+
'NodesPaletteWidget',
106108
'NodeTreeWidget',
107109
'NodesMenu',
108110
'Port',
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
from collections import defaultdict
4+
5+
from Qt import QtWidgets, QtCore, QtGui
6+
7+
from ..constants import URN_SCHEME
8+
9+
10+
class NodesGridDelagate(QtWidgets.QStyledItemDelegate):
11+
12+
def paint(self, painter, option, index):
13+
"""
14+
Args:
15+
painter (QtGui.QPainter):
16+
option (QtGui.QStyleOptionViewItem):
17+
index (QtCore.QModelIndex):
18+
"""
19+
if index.column() != 0:
20+
super(NodesGridDelagate, self).paint(painter, option, index)
21+
return
22+
23+
model = index.model().sourceModel()
24+
item = model.item(index.row(), index.column())
25+
26+
sub_margin = 2
27+
radius = 5
28+
29+
base_rect = QtCore.QRectF(
30+
option.rect.x() + sub_margin,
31+
option.rect.y() + sub_margin,
32+
option.rect.width() - (sub_margin * 2),
33+
option.rect.height() - (sub_margin * 2)
34+
)
35+
36+
painter.save()
37+
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
38+
39+
# background.
40+
bg_color = option.palette.window().color()
41+
pen_color = option.palette.midlight().color().lighter(120)
42+
if option.state & QtWidgets.QStyle.State_Selected:
43+
bg_color = bg_color.lighter(120)
44+
pen_color = pen_color.lighter(160)
45+
46+
pen = QtGui.QPen(pen_color, 3.0)
47+
pen.setCapStyle(QtCore.Qt.RoundCap)
48+
painter.setPen(pen)
49+
painter.setBrush(QtGui.QBrush(bg_color))
50+
painter.drawRoundRect(base_rect,
51+
int(base_rect.height()/radius),
52+
int(base_rect.width()/radius))
53+
54+
pen_color = option.palette.midlight().color().darker(130)
55+
if option.state & QtWidgets.QStyle.State_Selected:
56+
pen_color = option.palette.highlight().color()
57+
pen = QtGui.QPen(pen_color, 1.0)
58+
pen.setCapStyle(QtCore.Qt.RoundCap)
59+
painter.setPen(pen)
60+
painter.setBrush(QtCore.Qt.NoBrush)
61+
62+
sub_margin = 6
63+
sub_rect = QtCore.QRectF(
64+
base_rect.x() + sub_margin,
65+
base_rect.y() + sub_margin,
66+
base_rect.width() - (sub_margin * 2),
67+
base_rect.height() - (sub_margin * 2)
68+
)
69+
painter.drawRoundRect(sub_rect,
70+
int(sub_rect.height() / radius),
71+
int(sub_rect.width() / radius))
72+
73+
painter.setBrush(QtGui.QBrush(pen_color))
74+
edge_size = 2, sub_rect.height() - 6
75+
left_x = sub_rect.left()
76+
right_x = sub_rect.right() - edge_size[0]
77+
pos_y = sub_rect.center().y() - (edge_size[1] / 2)
78+
79+
for pos_x in [left_x, right_x]:
80+
painter.drawRect(QtCore.QRectF(
81+
pos_x, pos_y, edge_size[0], edge_size[1]
82+
))
83+
84+
# painter.setPen(QtCore.Qt.NoPen)
85+
painter.setBrush(QtGui.QBrush(bg_color))
86+
dot_size = 4
87+
left_x = sub_rect.left() - 1
88+
right_x = sub_rect.right() - (dot_size - 1)
89+
pos_y = sub_rect.center().y() - (dot_size / 2)
90+
for pos_x in [left_x, right_x]:
91+
painter.drawEllipse(QtCore.QRectF(
92+
pos_x, pos_y, dot_size, dot_size
93+
))
94+
pos_x -= dot_size + 2
95+
96+
# text
97+
pen_color = option.palette.text().color()
98+
pen = QtGui.QPen(pen_color, 0.5)
99+
pen.setCapStyle(QtCore.Qt.RoundCap)
100+
painter.setPen(pen)
101+
102+
font = painter.font()
103+
font_metrics = QtGui.QFontMetrics(font)
104+
font_width = font_metrics.width(item.text().replace(' ', '_'))
105+
font_height = font_metrics.height()
106+
text_rect = QtCore.QRectF(
107+
sub_rect.center().x() - (font_width / 2),
108+
sub_rect.center().y() - (font_height * 0.55),
109+
font_width, font_height)
110+
painter.drawText(text_rect, item.text())
111+
painter.restore()
112+
113+
114+
class NodesGridProxyModel(QtCore.QSortFilterProxyModel):
115+
116+
def __init__(self, parent=None):
117+
super(NodesGridProxyModel, self).__init__(parent)
118+
119+
def mimeData(self, indexes):
120+
node_ids = ['node:{}'.format(i.data(QtCore.Qt.ToolTipRole))
121+
for i in indexes]
122+
node_urn = URN_SCHEME + ';'.join(node_ids)
123+
mime_data = super(NodesGridProxyModel, self).mimeData(indexes)
124+
mime_data.setUrls([node_urn])
125+
return mime_data
126+
127+
128+
class NodesGridView(QtWidgets.QListView):
129+
130+
def __init__(self, parent=None):
131+
super(NodesGridView, self).__init__(parent)
132+
self.setSelectionMode(self.ExtendedSelection)
133+
self.setUniformItemSizes(True)
134+
self.setResizeMode(self.Adjust)
135+
self.setViewMode(self.IconMode)
136+
self.setDragDropMode(self.DragOnly)
137+
self.setDragEnabled(True)
138+
self.setMinimumSize(450, 300)
139+
self.setSpacing(4)
140+
141+
model = QtGui.QStandardItemModel()
142+
proxy_model = NodesGridProxyModel()
143+
proxy_model.setSourceModel(model)
144+
self.setModel(proxy_model)
145+
self.setItemDelegate(NodesGridDelagate(self))
146+
147+
def clear(self):
148+
self.model().sourceMode().clear()
149+
150+
def add_item(self, label, tooltip=''):
151+
item = QtGui.QStandardItem(label)
152+
item.setSizeHint(QtCore.QSize(130, 40))
153+
item.setToolTip(tooltip)
154+
model = self.model().sourceModel()
155+
model.appendRow(item)
156+
157+
158+
class NodesPaletteWidget(QtWidgets.QWidget):
159+
160+
def __init__(self, parent=None, node_graph=None):
161+
super(NodesPaletteWidget, self).__init__(parent)
162+
self.setWindowTitle('Nodes')
163+
164+
self._category_tabs = {}
165+
self._custom_labels = {}
166+
self._factory = node_graph.node_factory if node_graph else None
167+
168+
self._tab_widget = QtWidgets.QTabWidget()
169+
self._tab_widget.setMovable(True)
170+
171+
layout = QtWidgets.QVBoxLayout(self)
172+
layout.addWidget(self._tab_widget)
173+
174+
self._build_ui()
175+
176+
def __repr__(self):
177+
return '<{} object at {}>'.format(
178+
self.__class__.__name__, hex(id(self))
179+
)
180+
181+
def _build_ui(self):
182+
"""
183+
populate the ui
184+
"""
185+
categories = set()
186+
node_types = defaultdict(list)
187+
for name, node_ids in self._factory.names.items():
188+
for nid in node_ids:
189+
category = '.'.join(nid.split('.')[:-1])
190+
categories.add(category)
191+
node_types[category].append((nid, name))
192+
193+
for category, nodes_list in node_types.items():
194+
grid_view = self._add_category_tab(category)
195+
for node_id, node_name in nodes_list:
196+
grid_view.add_item(node_name, node_id)
197+
198+
def _set_node_factory(self, factory):
199+
"""
200+
Set current node factory.
201+
202+
Args:
203+
factory (NodeFactory): node factory.
204+
"""
205+
self._factory = factory
206+
207+
def _add_category_tab(self, category):
208+
"""
209+
Adds a new tab to the node palette widget.
210+
211+
Args:
212+
category (str): node identifier category eg. ``"nodes.widgets"``
213+
214+
Returns:
215+
NodesGridView: nodes grid view widget.
216+
"""
217+
if category not in self._category_tabs:
218+
grid_widget = NodesGridView(self)
219+
self._tab_widget.addTab(grid_widget, category)
220+
self._category_tabs[category] = grid_widget
221+
return self._category_tabs[category]
222+
223+
def set_category_label(self, category, label):
224+
"""
225+
Override tab label for a node category tab.
226+
227+
Args:
228+
category (str): node identifier category eg. ``"nodes.widgets"``
229+
label (str): custom display label. eg. ``"Node Widgets"``
230+
"""
231+
if label in self._custom_labels.values():
232+
labels = {v: k for k, v in self._custom_labels.items()}
233+
raise ValueError('label "{}" already in use for "{}"'
234+
.format(label, labels[label]))
235+
previous_label = self._custom_labels.get(category, '')
236+
for idx in range(self._tab_widget.count()):
237+
tab_text = self._tab_widget.tabText(idx)
238+
if tab_text in [category, previous_label]:
239+
self._tab_widget.setTabText(idx, label)
240+
break
241+
self._custom_labels[category] = label
242+
243+
def update(self):
244+
"""
245+
Update and refresh the node palette widget.
246+
"""
247+
self._build_tree()
248+
249+
250+
251+
252+

NodeGraphQt/widgets/node_tree.py renamed to NodeGraphQt/widgets/nodes_tree.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,18 @@ class NodeTreeWidget(QtWidgets.QTreeWidget):
3030
def __init__(self, parent=None, node_graph=None):
3131
super(NodeTreeWidget, self).__init__(parent)
3232
self.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly)
33+
self.setSelectionMode(self.ExtendedSelection)
3334
self.setHeaderHidden(True)
3435
self.setWindowTitle('Nodes')
35-
self._factory = None
36+
37+
self._factory = node_graph.node_factory if node_graph else None
3638
self._custom_labels = {}
37-
self._set_node_factory(node_graph.node_factory)
39+
self._category_items = {}
3840

3941
def __repr__(self):
40-
return '<{} object at {}>'.format(self.__class__.__name__, hex(id(self)))
42+
return '<{} object at {}>'.format(
43+
self.__class__.__name__, hex(id(self))
44+
)
4145

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

62-
category_items = {}
66+
self._category_items = {}
6367
for category in sorted(categories):
6468
if category in self._custom_labels.keys():
6569
label = self._custom_labels[category]
@@ -72,11 +76,11 @@ def _build_tree(self):
7276
cat_item.setSizeHint(0, QtCore.QSize(100, 26))
7377
self.addTopLevelItem(cat_item)
7478
cat_item.setExpanded(True)
75-
category_items[category] = cat_item
79+
self._category_items[category] = cat_item
7680

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

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

96100
def set_category_label(self, category, label):
97101
"""
98-
Set custom label for a node category root item.
102+
Override the label for a node category root item.
99103
100104
.. image:: _images/nodes_tree_category_label.png
101105
:width: 70%
@@ -105,6 +109,9 @@ def set_category_label(self, category, label):
105109
label (str): custom display label. eg. ``"Node Widgets"``
106110
"""
107111
self._custom_labels[category] = label
112+
if category in self._category_items:
113+
item = self._category_items[category]
114+
item.setText(0, label)
108115

109116
def update(self):
110117
"""

docs/_images/nodes_palette.png

81.5 KB
Loading

docs/custom_widgets.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,36 @@ example
3333
:members:
3434
:exclude-members: property_changed
3535

36+
Nodes Palette
37+
*************
38+
39+
The :class:`NodeGraphQt.NodesPaletteWidget` is a widget for displaying all
40+
registered nodes from the node graph in a grid layout with this widget a user
41+
can create nodes by dragging and dropping.
42+
43+
.. image:: _images/nodes_palette.png
44+
:width: 400px
45+
46+
example
47+
48+
.. code-block:: python
49+
:linenos:
50+
51+
from NodeGraphQt import NodeGraph, NodesPaletteWidget
52+
53+
# create node graph.
54+
graph = NodeGraph()
55+
56+
# create nodes palette widget.
57+
nodes_palette = NodesPaletteWidget(parent=None, node_graph=graph)
58+
nodes_palette.show()
59+
60+
----
61+
62+
.. autoclass:: NodeGraphQt.NodesPaletteWidget
63+
:members:
64+
:exclude-members: mimeData,
65+
3666
Nodes Tree
3767
**********
3868

0 commit comments

Comments
 (0)