diff --git a/NodeGraphQt/base/commands.py b/NodeGraphQt/base/commands.py index 184245b6..0fd3c4dd 100644 --- a/NodeGraphQt/base/commands.py +++ b/NodeGraphQt/base/commands.py @@ -121,6 +121,11 @@ def redo(self): self.model.nodes[self.node.id] = self.node self.viewer.add_node(self.node.view, self.pos) + # node width & height is calculated when its added to the scene + # so we have to update the node model here. + self.node.model.width = self.node.view.width + self.node.model.height = self.node.view.height + class NodeRemovedCmd(QtWidgets.QUndoCommand): """ diff --git a/NodeGraphQt/base/graph.py b/NodeGraphQt/base/graph.py index caa27ad6..d790f955 100644 --- a/NodeGraphQt/base/graph.py +++ b/NodeGraphQt/base/graph.py @@ -17,9 +17,10 @@ from NodeGraphQt.base.node import NodeObject from NodeGraphQt.base.port import Port from NodeGraphQt.constants import ( - NODE_LAYOUT_DIRECTION, NODE_LAYOUT_HORIZONTAL, NODE_LAYOUT_VERTICAL, + URI_SCHEME, + URN_SCHEME, + LayoutDirectionEnum, PipeLayoutEnum, - URI_SCHEME, URN_SCHEME, PortTypeEnum, ViewerEnum ) @@ -125,6 +126,15 @@ def __init__(self, parent=None, **kwargs): self.setObjectName('NodeGraph') self._model = ( kwargs.get('model') or NodeGraphModel()) + + layout_direction = kwargs.get('layout_direction') + if layout_direction: + if layout_direction not in [e.value for e in LayoutDirectionEnum]: + layout_direction = LayoutDirectionEnum.HORIZONTAL.value + self._model.layout_direction = layout_direction + else: + layout_direction = self._model.layout_direction + self._node_factory = ( kwargs.get('node_factory') or NodeFactory()) @@ -138,6 +148,7 @@ def __init__(self, parent=None, **kwargs): self._viewer = ( kwargs.get('viewer') or NodeViewer(undo_stack=self._undo_stack)) + self._viewer.set_layout_direction(layout_direction) self._build_context_menu() self._register_builtin_nodes() @@ -786,6 +797,52 @@ def set_pipe_style(self, style=PipeLayoutEnum.CURVED.value): style = style if 0 <= style <= pipe_max else PipeLayoutEnum.CURVED.value self._viewer.set_pipe_layout(style) + def layout_direction(self): + """ + Return the current node graph layout direction. + + `Implemented in` ``v0.3.0`` + + See Also: + :meth:`NodeGraph.set_layout_direction` + + Returns: + int: layout direction. + """ + return self.model.layout_direction + + def set_layout_direction(self, direction): + """ + Sets the node graph layout direction to horizontal or vertical. + This function will also override the layout direction on all + nodes in the current node graph. + + `Implemented in` ``v0.3.0`` + + See Also: + :meth:`NodeGraph.layout_direction`, + :meth:`NodeObject.set_layout_direction` + + Note: + Node Graph Layout Types: + + * :attr:`NodeGraphQt.constants.LayoutDirectionEnum.HORIZONTAL` + * :attr:`NodeGraphQt.constants.LayoutDirectionEnum.VERTICAL` + + Warnings: + This function does not register to the undo stack. + + Args: + direction (int): layout direction. + """ + direction_types = [e.value for e in LayoutDirectionEnum] + if direction not in direction_types: + direction = LayoutDirectionEnum.HORIZONTAL.value + self._model.layout_direction = direction + for node in self.all_nodes(): + node.set_layout_direction(direction) + self._viewer.set_layout_direction(direction) + def fit_to_selection(self): """ Sets the zoom level to fit selected nodes. @@ -853,7 +910,7 @@ def register_node(self, node, alias=None): Register the node to the :meth:`NodeGraph.node_factory` Args: - node (_NodeGraphQt.NodeObject): node object. + node (NodeGraphQt.NodeObject): node object. alias (str): custom alias name for the node type. """ self._node_factory.register_node(node, alias) @@ -922,6 +979,9 @@ def format_color(clr): if pos: node.model.pos = [float(pos[0]), float(pos[1])] + # initial node direction layout. + node.model.layout_direction = self.layout_direction() + node.update() if push_undo: @@ -964,6 +1024,11 @@ def add_node(self, node, pos=None, selected=True, push_undo=True): node.NODE_NAME = self.get_unique_name(node.NODE_NAME) node.model._graph_model = self.model node.model.name = node.NODE_NAME + + # initial node direction layout. + node.model.layout_direction = self.layout_direction() + + # update method must be called before it's been added to the viewer. node.update() if push_undo: @@ -1672,7 +1737,9 @@ def auto_layout_nodes(self, nodes=None, down_stream=True, start_nodes=None): else: rank_map[rank] = [node] - if NODE_LAYOUT_DIRECTION is NODE_LAYOUT_HORIZONTAL: + node_layout_direction = self._viewer.get_layout_direction() + + if node_layout_direction is LayoutDirectionEnum.HORIZONTAL.value: current_x = 0 node_height = 120 for rank in sorted(range(len(rank_map)), reverse=not down_stream): @@ -1687,7 +1754,7 @@ def auto_layout_nodes(self, nodes=None, down_stream=True, start_nodes=None): current_y += dy * 0.5 + 10 current_x += max_width * 0.5 + 100 - elif NODE_LAYOUT_DIRECTION is NODE_LAYOUT_VERTICAL: + elif node_layout_direction is LayoutDirectionEnum.VERTICAL.value: current_y = 0 node_width = 250 for rank in sorted(range(len(rank_map)), reverse=not down_stream): @@ -1861,7 +1928,11 @@ def expand_group_node(self, node): # build new sub graph. node_factory = copy.deepcopy(self.node_factory) - sub_graph = SubGraph(self, node=node, node_factory=node_factory) + layout_direction = self.layout_direction() + sub_graph = SubGraph(self, + node=node, + node_factory=node_factory, + layout_direction=layout_direction) # populate the sub graph. session = node.get_sub_graph_session() @@ -1913,14 +1984,17 @@ class SubGraph(NodeGraph): - """ - def __init__(self, parent=None, node=None, node_factory=None): + def __init__(self, parent=None, node=None, node_factory=None, **kwargs): """ Args: parent (object): object parent. node (GroupNode): group node related to this sub graph. node_factory (NodeFactory): override node factory. + **kwargs (dict): additional kwargs. """ - super(SubGraph, self).__init__(parent, node_factory=node_factory) + super(SubGraph, self).__init__( + parent, node_factory=node_factory, **kwargs + ) # sub graph attributes. self._node = node @@ -1953,6 +2027,8 @@ def _build_port_nodes(self): Returns: tuple(dict, dict): input nodes, output nodes. """ + node_layout_direction = self._viewer.get_layout_direction() + # build the parent input port nodes. input_nodes = {n.name(): n for n in self.get_nodes_by_type(PortInputNode.type_)} @@ -1965,9 +2041,9 @@ def _build_port_nodes(self): input_nodes[port.name()] = input_node self.add_node(input_node, selected=False, push_undo=False) x, y = input_node.pos() - if NODE_LAYOUT_DIRECTION is NODE_LAYOUT_HORIZONTAL: + if node_layout_direction is LayoutDirectionEnum.HORIZONTAL.value: x -= 100 - elif NODE_LAYOUT_DIRECTION is NODE_LAYOUT_VERTICAL: + elif node_layout_direction is LayoutDirectionEnum.VERTICAL.value: y -= 100 input_node.set_property('pos', [x, y], push_undo=False) @@ -1983,9 +2059,9 @@ def _build_port_nodes(self): output_nodes[port.name()] = output_node self.add_node(output_node, selected=False, push_undo=False) x, y = output_node.pos() - if NODE_LAYOUT_DIRECTION is NODE_LAYOUT_HORIZONTAL: + if node_layout_direction is LayoutDirectionEnum.HORIZONTAL.value: x += 100 - elif NODE_LAYOUT_DIRECTION is NODE_LAYOUT_VERTICAL: + elif node_layout_direction is LayoutDirectionEnum.VERTICAL.value: y += 100 output_node.set_property('pos', [x, y], push_undo=False) @@ -2282,7 +2358,10 @@ def expand_group_node(self, node): # build new sub graph. node_factory = copy.deepcopy(self.node_factory) - sub_graph = SubGraph(self, node=node, node_factory=node_factory) + sub_graph = SubGraph(self, + node=node, + node_factory=node_factory, + layout_direction=self.layout_direction()) # populate the sub graph. serialized_session = node.get_sub_graph_session() diff --git a/NodeGraphQt/base/graph_actions.py b/NodeGraphQt/base/graph_actions.py index b77fe3d6..18259627 100644 --- a/NodeGraphQt/base/graph_actions.py +++ b/NodeGraphQt/base/graph_actions.py @@ -8,11 +8,11 @@ def build_context_menu(graph): graph (NodeGraphQt.NodeGraph): node graph controller. """ from Qt import QtGui, QtCore - graph_menu = graph.get_context_menu('graph') + context_menu = graph.get_context_menu('graph') # "File" menu. # -------------------------------------------------------------------------- - file_menu = graph_menu.add_menu('&File') + file_menu = context_menu.add_menu('&File') file_menu.add_command('Open...', _open_session, QtGui.QKeySequence.Open) file_menu.add_command('Import...', _import_session) @@ -22,7 +22,7 @@ def build_context_menu(graph): # "Edit" menu. # -------------------------------------------------------------------------- - edit_menu = graph_menu.add_menu('&Edit') + edit_menu = context_menu.add_menu('&Edit') edit_menu.add_separator() edit_menu.add_command('Clear Undo History', _clear_undo) @@ -49,20 +49,24 @@ def build_context_menu(graph): edit_menu.add_command('Zoom Out', _zoom_out, '-') edit_menu.add_command('Reset Zoom', _reset_zoom, 'H') - edit_menu.add_separator() + context_menu.add_separator() - # "Grid Mode" submenu. + # "Node" menu. # -------------------------------------------------------------------------- - bg_menu = edit_menu.add_menu('&Grid Mode') - bg_menu.add_command('None', _bg_grid_none) - bg_menu.add_command('Lines', _bg_grid_lines) - bg_menu.add_command('Dots', _bg_grid_dots) + graph_menu = context_menu.add_menu('&Graph') - edit_menu.add_separator() + bg_menu = graph_menu.add_menu('&Background') + bg_menu.add_command('None', _bg_grid_none, 'Alt+1') + bg_menu.add_command('Lines', _bg_grid_lines, 'Alt+2') + bg_menu.add_command('Dots', _bg_grid_dots, 'Alt+3') + + layout_menu = graph_menu.add_menu('&Layout') + layout_menu.add_command('Horizontal', _layout_h_mode, 'Shift+1') + layout_menu.add_command('Vertical', _layout_v_mode, 'Shift+2') # "Node" menu. # -------------------------------------------------------------------------- - node_menu = graph_menu.add_menu('&Nodes') + node_menu = context_menu.add_menu('&Nodes') node_menu.add_command('Node Search', _toggle_node_search, 'Tab') node_menu.add_separator() node_menu.add_command( @@ -76,7 +80,7 @@ def build_context_menu(graph): # "Pipe" menu. # -------------------------------------------------------------------------- - pipe_menu = graph_menu.add_menu('&Pipes') + pipe_menu = context_menu.add_menu('&Pipes') pipe_menu.add_command('Curved', _curved_pipe) pipe_menu.add_command('Straight', _straight_pipe) pipe_menu.add_command('Angle', _angle_pipe) @@ -109,6 +113,20 @@ def _reset_zoom(graph): graph.reset_zoom() +def _layout_h_mode(graph): + """ + Set node graph layout direction to horizontal. + """ + graph.set_layout_direction(0) + + +def _layout_v_mode(graph): + """ + Set node graph layout direction to vertical. + """ + graph.set_layout_direction(1) + + def _open_session(graph): """ Prompts a file open dialog to load a session. diff --git a/NodeGraphQt/base/model.py b/NodeGraphQt/base/model.py index ac1d8710..5bd59e04 100644 --- a/NodeGraphQt/base/model.py +++ b/NodeGraphQt/base/model.py @@ -3,6 +3,7 @@ from collections import defaultdict from NodeGraphQt.constants import ( + LayoutDirectionEnum, NODE_PROP, NODE_PROP_QLABEL, NODE_PROP_QLINEEDIT, @@ -73,6 +74,7 @@ def __init__(self): self.width = 100.0 self.height = 80.0 self.pos = [0.0, 0.0] + self.layout_direction = LayoutDirectionEnum.HORIZONTAL.value # BaseNode attrs. self.inputs = {} @@ -107,6 +109,7 @@ def __init__(self): 'width': NODE_PROP, 'height': NODE_PROP, 'pos': NODE_PROP, + 'layout_direction': NODE_PROP, 'inputs': NODE_PROP, 'outputs': NODE_PROP, } @@ -230,6 +233,7 @@ def to_dict(self): 'width': 0.0, 'height: 0.0, 'pos': (0.0, 0.0), + 'layout_direction': 0, 'custom': {}, 'inputs': { : {: [, ]} @@ -317,6 +321,7 @@ def __init__(self): self.session = '' self.acyclic = True self.pipe_collision = False + self.layout_direction = LayoutDirectionEnum.HORIZONTAL.value def common_properties(self): """ diff --git a/NodeGraphQt/base/node.py b/NodeGraphQt/base/node.py index 57fb4340..d48fdc46 100644 --- a/NodeGraphQt/base/node.py +++ b/NodeGraphQt/base/node.py @@ -1,10 +1,7 @@ #!/usr/bin/python from NodeGraphQt.base.commands import PropertyChangedCmd from NodeGraphQt.base.model import NodeModel -from NodeGraphQt.constants import (NODE_PROP, - NODE_LAYOUT_DIRECTION, - NODE_LAYOUT_VERTICAL, - NODE_LAYOUT_HORIZONTAL) +from NodeGraphQt.constants import NODE_PROP class _ClassProperty(object): @@ -26,9 +23,7 @@ class NodeObject(object): :class:`NodeGraphQt.BackdropNode` Args: - qgraphics_views (dict): Dictionary with the node layout type as the key - and a custom graphics item subclassed from the ``AbstractNodeItem`` - as the value. + qgraphics_item (AbstractNodeItem): QGraphicsItem item used for drawing. .. code-block:: python @@ -36,11 +31,8 @@ class NodeObject(object): class BaseNode(NodeObject): - def __init__(self, qgraphics_views=None): - qgraphics_views = qgraphics_views or { - NodeGraphQt.constants.NODE_LAYOUT_HORIZONTAL: NodeItem, - NodeGraphQt.constants.NODE_LAYOUT_VERTICAL: NodeItemVertical - } + def __init__(self, qgraphics_item=None): + qgraphics_item = qgraphics_item or NodeItem super(BaseNode, self).__init__(qgraphics_views) """ @@ -51,29 +43,27 @@ def __init__(self, qgraphics_views=None): # Base node name. NODE_NAME = None - def __init__(self, qgraphics_views=None): + def __init__(self, qgraphics_item=None): + """ + Args: + qgraphics_item (AbstractNodeItem): QGraphicsItem used for drawing. + """ self._graph = None self._model = NodeModel() self._model.type_ = self.type_ self._model.name = self.NODE_NAME - _NodeItem = None - if NODE_LAYOUT_DIRECTION is NODE_LAYOUT_VERTICAL: - _NodeItem = qgraphics_views.get(NODE_LAYOUT_VERTICAL) - elif NODE_LAYOUT_DIRECTION is NODE_LAYOUT_HORIZONTAL: - _NodeItem = qgraphics_views.get(NODE_LAYOUT_HORIZONTAL) - + _NodeItem = qgraphics_item if _NodeItem is None: - raise ValueError( - 'qgraphics item for the {} node layout can\'t be None!'.format({ - NODE_LAYOUT_VERTICAL: 'vertical', - NODE_LAYOUT_HORIZONTAL: 'horizontal' - }[NODE_LAYOUT_DIRECTION])) + raise RuntimeError( + 'No qgraphics item specified for the node object!' + ) self._view = _NodeItem() self._view.type_ = self.type_ self._view.name = self.model.name self._view.id = self._model.id + self._view.layout_direction = self._model.layout_direction def __repr__(self): return '<{}("{}") object at {}>'.format( @@ -217,6 +207,7 @@ def serialize(self): 'width': 0.0, 'height: 0.0, 'pos': (0.0, 0.0), + 'layout_direction': 0, 'custom': {}, } } @@ -469,3 +460,36 @@ def pos(self): self.model.pos = self.view.xy_pos return self.model.pos + + def layout_direction(self): + """ + Returns layout direction for this node. + + See Also: + :meth:`NodeObject.set_layout_direction` + + Returns: + int: node layout direction. + """ + return self.model.layout_direction + + def set_layout_direction(self, value=0): + """ + Sets the node layout direction to either horizontal or vertical on + the current node only. + + `Implemented in` ``v0.3.0`` + + See Also: + :meth:`NodeGraph.set_layout_direction` + :meth:`NodeObject.layout_direction` + + Warnings: + This function does not register to the undo stack. + + Args: + value (int): layout direction mode. + """ + self.model.layout_direction = value + self.view.layout_direction = value + diff --git a/NodeGraphQt/constants.py b/NodeGraphQt/constants.py index ca70c7be..47f53954 100644 --- a/NodeGraphQt/constants.py +++ b/NodeGraphQt/constants.py @@ -17,38 +17,24 @@ URI_SCHEME = 'nodegraphqt://' URN_SCHEME = 'nodegraphqt::' -# === PATHS === - +# PATHS BASE_PATH = os.path.dirname(os.path.abspath(__file__)) ICON_PATH = os.path.join(BASE_PATH, 'widgets', 'icons') ICON_DOWN_ARROW = os.path.join(ICON_PATH, 'down_arrow.png') ICON_NODE_BASE = os.path.join(ICON_PATH, 'node_base.png') -# === DRAW STACK ORDER === - +# DRAW STACK ORDER Z_VAL_PIPE = -1 Z_VAL_NODE = 1 Z_VAL_PORT = 2 Z_VAL_NODE_WIDGET = 3 -# === ITEM CACHE MODE === - +# ITEM CACHE MODE # QGraphicsItem.NoCache # QGraphicsItem.DeviceCoordinateCache # QGraphicsItem.ItemCoordinateCache - ITEM_CACHE_MODE = QtWidgets.QGraphicsItem.DeviceCoordinateCache -# === NODE LAYOUT DIRECTION === - -#: Mode for vertical node layout. -NODE_LAYOUT_VERTICAL = 0 -#: Mode for horizontal node layout. -NODE_LAYOUT_HORIZONTAL = 1 -#: Variable for setting the node layout direction. -# NODE_LAYOUT_DIRECTION = NODE_LAYOUT_VERTICAL -NODE_LAYOUT_DIRECTION = NODE_LAYOUT_HORIZONTAL - # =================================== GLOBAL =================================== @@ -66,6 +52,18 @@ class VersionEnum(Enum): #: PATCH = int(_v.split('.')[2]) + +class LayoutDirectionEnum(Enum): + """ + Node graph nodes layout direction: + :py:mod:`NodeGraphQt.constants.ViewerLayoutEnum` + """ + #: layout nodes left to right. + HORIZONTAL = 0 + #: layout nodes top to bottom. + VERTICAL = 1 + + # =================================== VIEWER =================================== diff --git a/NodeGraphQt/nodes/backdrop_node.py b/NodeGraphQt/nodes/backdrop_node.py index 9da573c0..628cd570 100644 --- a/NodeGraphQt/nodes/backdrop_node.py +++ b/NodeGraphQt/nodes/backdrop_node.py @@ -1,8 +1,6 @@ #!/usr/bin/python from NodeGraphQt.base.node import NodeObject -from NodeGraphQt.constants import (NODE_PROP_QTEXTEDIT, - NODE_LAYOUT_HORIZONTAL, - NODE_LAYOUT_VERTICAL) +from NodeGraphQt.constants import NODE_PROP_QTEXTEDIT from NodeGraphQt.qgraphics.node_backdrop import BackdropNodeItem @@ -22,11 +20,7 @@ class BackdropNode(NodeObject): NODE_NAME = 'Backdrop' def __init__(self, qgraphics_views=None): - qgraphics_views = qgraphics_views or { - NODE_LAYOUT_HORIZONTAL: BackdropNodeItem, - NODE_LAYOUT_VERTICAL: BackdropNodeItem - } - super(BackdropNode, self).__init__(qgraphics_views) + super(BackdropNode, self).__init__(qgraphics_views or BackdropNodeItem) # override base default color. self.model.color = (5, 129, 138, 255) self.create_property('backdrop_text', '', diff --git a/NodeGraphQt/nodes/base_node.py b/NodeGraphQt/nodes/base_node.py index 8ad5a84e..f8c04d17 100644 --- a/NodeGraphQt/nodes/base_node.py +++ b/NodeGraphQt/nodes/base_node.py @@ -7,13 +7,11 @@ NODE_PROP_QLINEEDIT, NODE_PROP_QCOMBO, NODE_PROP_QCHECKBOX, - PortTypeEnum, - NODE_LAYOUT_VERTICAL, - NODE_LAYOUT_HORIZONTAL) + PortTypeEnum) from NodeGraphQt.errors import (PortError, PortRegistrationError, NodeWidgetError) -from NodeGraphQt.qgraphics.node_base import NodeItem, NodeItemVertical +from NodeGraphQt.qgraphics.node_base import NodeItem from NodeGraphQt.widgets.node_widgets import (NodeBaseWidget, NodeComboBox, NodeLineEdit, @@ -57,21 +55,11 @@ def __init__(self): NODE_NAME = 'Node' - def __init__(self, qgraphics_views=None): - qgraphics_views = qgraphics_views or { - NODE_LAYOUT_HORIZONTAL: NodeItem, - NODE_LAYOUT_VERTICAL: NodeItemVertical - } - super(BaseNode, self).__init__(qgraphics_views) + def __init__(self, qgraphics_item=None): + super(BaseNode, self).__init__(qgraphics_item or NodeItem) self._inputs = [] self._outputs = [] - def draw(self): - """ - Redraws the node in the scene. - """ - self.view.draw_node() - def update_model(self): """ Update the node model from view. @@ -84,6 +72,29 @@ def update_model(self): for name, widget in self.view.widgets.items(): self.model.set_property(name, widget.value) + def set_layout_direction(self, value=0): + """ + Sets the node layout direction to either horizontal or vertical on + the current node only. + + `Implemented in` ``v0.3.0`` + + See Also: + :meth:`NodeGraph.set_layout_direction`, + :meth:`NodeObject.layout_direction` + + + Warnings: + This function does not register to the undo stack. + + Args: + value (int): layout direction mode. + """ + # base logic to update the model and view attributes only. + super(BaseNode, self).set_layout_direction(value) + # redraw the node. + self._view.draw_node() + def set_icon(self, icon=None): """ Set the node icon. @@ -372,7 +383,7 @@ def delete_input(self, port): self._model.inputs.pop(port.name()) self._view.delete_input(port.view) port.model.node = None - self.draw() + self._view.draw_node() def delete_output(self, port): """ @@ -402,7 +413,7 @@ def delete_output(self, port): self._model.outputs.pop(port.name()) self._view.delete_output(port.view) port.model.node = None - self.draw() + self._view.draw_node() def set_port_deletion_allowed(self, mode=False): """ @@ -490,7 +501,7 @@ def set_ports(self, port_data): display_name=port['display_name'], locked=port.get('locked') or False) for port in port_data['output_ports']] - self.draw() + self._view.draw_node() def inputs(self): """ diff --git a/NodeGraphQt/nodes/group_node.py b/NodeGraphQt/nodes/group_node.py index 595c7891..c9b78d88 100644 --- a/NodeGraphQt/nodes/group_node.py +++ b/NodeGraphQt/nodes/group_node.py @@ -1,15 +1,13 @@ #!/usr/bin/python -from NodeGraphQt.constants import (NODE_LAYOUT_VERTICAL, - NODE_LAYOUT_HORIZONTAL) - from NodeGraphQt.nodes.base_node import BaseNode from NodeGraphQt.nodes.port_node import PortInputNode, PortOutputNode -from NodeGraphQt.qgraphics.node_group import (GroupNodeItem, - GroupNodeVerticalItem) +from NodeGraphQt.qgraphics.node_group import GroupNodeItem class GroupNode(BaseNode): """ + `Implemented in` ``v0.2.0`` + The ``NodeGraphQt.GroupNode`` class extends from the :class:``NodeGraphQt.BaseNode`` class with the ability to nest other nodes inside of it. @@ -24,12 +22,8 @@ class GroupNode(BaseNode): NODE_NAME = 'Group' - def __init__(self, qgraphics_views=None): - qgraphics_views = qgraphics_views or { - NODE_LAYOUT_HORIZONTAL: GroupNodeItem, - NODE_LAYOUT_VERTICAL: GroupNodeVerticalItem - } - super(GroupNode, self).__init__(qgraphics_views) + def __init__(self, qgraphics_item=None): + super(GroupNode, self).__init__(qgraphics_item or GroupNodeItem) self._input_port_nodes = {} self._output_port_nodes = {} diff --git a/NodeGraphQt/nodes/port_node.py b/NodeGraphQt/nodes/port_node.py index f7ddc2f2..fed4d3bc 100644 --- a/NodeGraphQt/nodes/port_node.py +++ b/NodeGraphQt/nodes/port_node.py @@ -1,13 +1,8 @@ #!/usr/bin/python -from NodeGraphQt.constants import (NODE_LAYOUT_VERTICAL, - NODE_LAYOUT_HORIZONTAL) - from NodeGraphQt.errors import PortRegistrationError from NodeGraphQt.nodes.base_node import BaseNode -from NodeGraphQt.qgraphics.node_port_in import (PortInputNodeItem, - PortInputNodeVerticalItem) -from NodeGraphQt.qgraphics.node_port_out import (PortOutputNodeItem, - PortOutputNodeVerticalItem) +from NodeGraphQt.qgraphics.node_port_in import PortInputNodeItem +from NodeGraphQt.qgraphics.node_port_out import PortOutputNodeItem class PortInputNode(BaseNode): @@ -26,12 +21,8 @@ class PortInputNode(BaseNode): NODE_NAME = 'InputPort' - def __init__(self, qgraphics_views=None, parent_port=None): - qgraphics_views = qgraphics_views or { - NODE_LAYOUT_HORIZONTAL: PortInputNodeItem, - NODE_LAYOUT_VERTICAL: PortInputNodeVerticalItem - } - super(PortInputNode, self).__init__(qgraphics_views) + def __init__(self, qgraphics_item=None, parent_port=None): + super(PortInputNode, self).__init__(qgraphics_item or PortInputNodeItem) self._parent_port = parent_port @property @@ -73,8 +64,8 @@ def add_output(self, name='output', multi_output=True, display_name=True, class PortOutputNode(BaseNode): """ - The ``PortOutputNode`` class is the node object that represents a port from a - :class:`NodeGraphQt.GroupNode` when expanded in a + The ``PortOutputNode`` class is the node object that represents a port + from a :class:`NodeGraphQt.GroupNode` when expanded in a :class:`NodeGraphQt.SubGraph`. **Inherited from:** :class:`NodeGraphQt.BaseNode` @@ -87,12 +78,10 @@ class PortOutputNode(BaseNode): NODE_NAME = 'OutputPort' - def __init__(self, qgraphics_views=None, parent_port=None): - qgraphics_views = qgraphics_views or { - NODE_LAYOUT_HORIZONTAL: PortOutputNodeItem, - NODE_LAYOUT_VERTICAL: PortOutputNodeVerticalItem - } - super(PortOutputNode, self).__init__(qgraphics_views) + def __init__(self, qgraphics_item=None, parent_port=None): + super(PortOutputNode, self).__init__( + qgraphics_item or PortOutputNodeItem + ) self._parent_port = parent_port @property diff --git a/NodeGraphQt/pkg_info.py b/NodeGraphQt/pkg_info.py index 4f361c88..4e7a22a8 100644 --- a/NodeGraphQt/pkg_info.py +++ b/NodeGraphQt/pkg_info.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -__version__ = '0.2.2' +__version__ = '0.3.0' __status__ = 'Work in Progress' __license__ = 'MIT' diff --git a/NodeGraphQt/qgraphics/node_abstract.py b/NodeGraphQt/qgraphics/node_abstract.py index ca996da3..29dfcf18 100644 --- a/NodeGraphQt/qgraphics/node_abstract.py +++ b/NodeGraphQt/qgraphics/node_abstract.py @@ -1,7 +1,12 @@ #!/usr/bin/python from Qt import QtCore, QtWidgets -from NodeGraphQt.constants import Z_VAL_NODE, NodeEnum, ITEM_CACHE_MODE +from NodeGraphQt.constants import ( + Z_VAL_NODE, + ITEM_CACHE_MODE, + LayoutDirectionEnum, + NodeEnum +) class AbstractNodeItem(QtWidgets.QGraphicsItem): @@ -24,6 +29,7 @@ def __init__(self, name='node', parent=None): 'selected': False, 'disabled': False, 'visible': False, + 'layout_direction': LayoutDirectionEnum.HORIZONTAL.value, } self._width = NodeEnum.WIDTH.value self._height = NodeEnum.HEIGHT.value @@ -85,6 +91,14 @@ def type_(self): def type_(self, node_type='NODE'): self._properties['type_'] = node_type + @property + def layout_direction(self): + return self._properties['layout_direction'] + + @layout_direction.setter + def layout_direction(self, value=0): + self._properties['layout_direction'] = value + @property def size(self): return self._width, self._height diff --git a/NodeGraphQt/qgraphics/node_base.py b/NodeGraphQt/qgraphics/node_base.py index 9bb8dc4c..ada4ae9f 100644 --- a/NodeGraphQt/qgraphics/node_base.py +++ b/NodeGraphQt/qgraphics/node_base.py @@ -6,6 +6,7 @@ from NodeGraphQt.constants import ( ITEM_CACHE_MODE, ICON_NODE_BASE, + LayoutDirectionEnum, NodeEnum, PortEnum, PortTypeEnum, @@ -46,18 +47,27 @@ def __init__(self, name='node', parent=None): self._proxy_mode = False self._proxy_mode_threshold = 70 - def paint(self, painter, option, widget): + def post_init(self, viewer, pos=None): """ - Draws the node base not the ports or text. + Called after node has been added into the scene. Args: - painter (QtGui.QPainter): painter used for drawing the item. - option (QtGui.QStyleOptionGraphicsItem): - used to describe the parameters needed to draw. - widget (QtWidgets.QWidget): not used. - """ - self.auto_switch_mode() - + viewer (NodeGraphQt.widgets.viewer.NodeViewer): main viewer + pos (tuple): the cursor pos if node is called with tab search. + """ + if self.layout_direction == LayoutDirectionEnum.VERTICAL.value: + font = QtGui.QFont() + font.setPointSize(15) + self.text_item.setFont(font) + + # hide port text items for vertical layout. + if self.layout_direction is LayoutDirectionEnum.VERTICAL.value: + for text_item in self._input_items.values(): + text_item.setVisible(False) + for text_item in self._output_items.values(): + text_item.setVisible(False) + + def _paint_horizontal(self, painter, option, widget): painter.save() painter.setPen(QtCore.Qt.NoPen) painter.setBrush(QtCore.Qt.NoBrush) @@ -115,6 +125,79 @@ def paint(self, painter, option, widget): painter.restore() + def _paint_vertical(self, painter, option, widget): + painter.save() + painter.setPen(QtCore.Qt.NoPen) + painter.setBrush(QtCore.Qt.NoBrush) + + # base background. + margin = 1.0 + rect = self.boundingRect() + rect = QtCore.QRectF(rect.left() + margin, + rect.top() + margin, + rect.width() - (margin * 2), + rect.height() - (margin * 2)) + + radius = 4.0 + painter.setBrush(QtGui.QColor(*self.color)) + painter.drawRoundedRect(rect, radius, radius) + + # light overlay on background when selected. + if self.selected: + painter.setBrush( + QtGui.QColor(*NodeEnum.SELECTED_COLOR.value) + ) + painter.drawRoundedRect(rect, radius, radius) + + # top & bottom edge background. + padding = 2.0 + height = 10 + if self.selected: + painter.setBrush(QtGui.QColor(*NodeEnum.SELECTED_COLOR.value)) + else: + painter.setBrush(QtGui.QColor(0, 0, 0, 80)) + for y in [rect.y() + padding, rect.height() - height - 1]: + edge_rect = QtCore.QRectF(rect.x() + padding, y, + rect.width() - (padding * 2), height) + painter.drawRoundedRect(edge_rect, 3.0, 3.0) + + # node border + border_width = 0.8 + border_color = QtGui.QColor(*self.border_color) + if self.selected: + border_width = 1.2 + border_color = QtGui.QColor( + *NodeEnum.SELECTED_BORDER_COLOR.value + ) + border_rect = QtCore.QRectF(rect.left(), rect.top(), + rect.width(), rect.height()) + + pen = QtGui.QPen(border_color, border_width) + pen.setCosmetic(self.viewer().get_zoom() < 0.0) + painter.setBrush(QtCore.Qt.NoBrush) + painter.setPen(pen) + painter.drawRoundedRect(border_rect, radius, radius) + + painter.restore() + + def paint(self, painter, option, widget): + """ + Draws the node base not the ports. + + Args: + painter (QtGui.QPainter): painter used for drawing the item. + option (QtGui.QStyleOptionGraphicsItem): + used to describe the parameters needed to draw. + widget (QtWidgets.QWidget): not used. + """ + self.auto_switch_mode() + if self.layout_direction is LayoutDirectionEnum.HORIZONTAL.value: + self._paint_horizontal(painter, option, widget) + elif self.layout_direction is LayoutDirectionEnum.VERTICAL.value: + self._paint_vertical(painter, option, widget) + else: + raise RuntimeError('Node graph layout direction not valid!') + def mousePressEvent(self, event): """ Re-implemented to ignore event if LMB is over port collision area. @@ -253,17 +336,7 @@ def reset_pipes(self): for pipe in port.connected_pipes: pipe.reset() - def calc_size(self, add_w=0.0, add_h=0.0): - """ - Calculates the minimum node size. - - Args: - add_w (float): additional width. - add_h (float): additional height. - - Returns: - tuple(float, float): width, height. - """ + def _calc_size_horizontal(self): # width, height from node name text. text_w = self._text_item.boundingRect().width() text_h = self._text_item.boundingRect().height() @@ -321,46 +394,116 @@ def calc_size(self, add_w=0.0, add_h=0.0): # add bottom margin for node widget. height += 4.0 height *= 1.05 + return width, height - # additional width, height. - width += add_w - height += add_h + def _calc_size_vertical(self): + p_input_width = 0.0 + p_output_width = 0.0 + p_input_height = 0.0 + p_output_height = 0.0 + for port in self._input_items.keys(): + if port.isVisible(): + p_input_width += port.boundingRect().width() + if not p_input_height: + p_input_height = port.boundingRect().height() + for port in self._output_items.keys(): + if port.isVisible(): + p_output_width += port.boundingRect().width() + if not p_output_height: + p_output_height = port.boundingRect().height() + + widget_width = 0.0 + widget_height = 0.0 + for widget in self._widgets.values(): + if widget.boundingRect().width() > widget_width: + widget_width = widget.boundingRect().width() + widget_height += widget.boundingRect().height() + + width = max([p_input_width, p_output_width, widget_width]) + height = p_input_height + p_output_height + widget_height return width, height - def align_icon(self, h_offset=0.0, v_offset=0.0): + def calc_size(self, add_w=0.0, add_h=0.0): """ - Align node icon to the default top left of the node. + Calculates the minimum node size. Args: - v_offset (float): additional vertical offset. - h_offset (float): additional horizontal offset. + add_w (float): additional width. + add_h (float): additional height. + + Returns: + tuple(float, float): width, height. """ + if self.layout_direction is LayoutDirectionEnum.HORIZONTAL.value: + width, height = self._calc_size_horizontal() + elif self.layout_direction is LayoutDirectionEnum.VERTICAL.value: + width, height = self._calc_size_vertical() + else: + raise RuntimeError('Node graph layout direction not valid!') + + # additional width, height. + width += add_w + height += add_h + return width, height + + def _align_icon_horizontal(self, h_offset, v_offset): icon_rect = self._icon_item.boundingRect() text_rect = self._text_item.boundingRect() x = self.boundingRect().left() + 2.0 y = text_rect.center().y() - (icon_rect.height() / 2) self._icon_item.setPos(x + h_offset, y + v_offset) - def align_label(self, h_offset=0.0, v_offset=0.0): + def _align_icon_vertical(self, h_offset, v_offset): + center_y = self.boundingRect().center().y() + icon_rect = self._icon_item.boundingRect() + text_rect = self._text_item.boundingRect() + x = self.boundingRect().right() + h_offset + y = center_y - text_rect.height() - (icon_rect.height() / 2) + v_offset + self._icon_item.setPos(x, y) + + def align_icon(self, h_offset=0.0, v_offset=0.0): """ - Center node label text to the top of the node. + Align node icon to the default top left of the node. Args: - v_offset (float): vertical offset. - h_offset (float): horizontal offset. + v_offset (float): additional vertical offset. + h_offset (float): additional horizontal offset. """ + if self.layout_direction is LayoutDirectionEnum.HORIZONTAL.value: + self._align_icon_horizontal(h_offset, v_offset) + elif self.layout_direction is LayoutDirectionEnum.VERTICAL.value: + self._align_icon_vertical(h_offset, v_offset) + else: + raise RuntimeError('Node graph layout direction not valid!') + + def _align_label_horizontal(self, h_offset, v_offset): rect = self.boundingRect() text_rect = self._text_item.boundingRect() x = rect.center().x() - (text_rect.width() / 2) self._text_item.setPos(x + h_offset, rect.y() + v_offset) - def align_widgets(self, v_offset=0.0): + def _align_label_vertical(self, h_offset, v_offset): + rect = self._text_item.boundingRect() + x = self.boundingRect().right() + h_offset + y = self.boundingRect().center().y() - (rect.height() / 2) + v_offset + self.text_item.setPos(x, y) + + def align_label(self, h_offset=0.0, v_offset=0.0): """ - Align node widgets to the default center of the node. + Center node label text to the top of the node. Args: v_offset (float): vertical offset. + h_offset (float): horizontal offset. """ + if self.layout_direction is LayoutDirectionEnum.HORIZONTAL.value: + self._align_label_horizontal(h_offset, v_offset) + elif self.layout_direction is LayoutDirectionEnum.VERTICAL.value: + self._align_label_vertical(h_offset, v_offset) + else: + raise RuntimeError('Node graph layout direction not valid!') + + def _align_widgets_horizontal(self, v_offset): if not self._widgets: return rect = self.boundingRect() @@ -381,13 +524,39 @@ def align_widgets(self, v_offset=0.0): widget.setPos(x, y) y += widget_rect.height() - def align_ports(self, v_offset=0.0): + def _align_widgets_vertical(self, v_offset): + if not self._widgets: + return + rect = self.boundingRect() + y = rect.center().y() + v_offset + widget_height = 0.0 + for widget in self._widgets.values(): + widget_rect = widget.boundingRect() + widget_height += widget_rect.height() + y -= widget_height / 2 + + for widget in self._widgets.values(): + widget_rect = widget.boundingRect() + x = rect.center().x() - (widget_rect.width() / 2) + widget.widget().setTitleAlign('center') + widget.setPos(x, y) + y += widget_rect.height() + + def align_widgets(self, v_offset=0.0): """ - Align input, output ports in the node layout. + Align node widgets to the default center of the node. Args: - v_offset (float): port vertical offset. + v_offset (float): vertical offset. """ + if self.layout_direction is LayoutDirectionEnum.HORIZONTAL.value: + self._align_widgets_horizontal(v_offset) + elif self.layout_direction is LayoutDirectionEnum.VERTICAL.value: + self._align_widgets_vertical(v_offset) + else: + raise RuntimeError('Node graph layout direction not valid!') + + def _align_ports_horizontal(self, v_offset): width = self._width txt_offset = PortEnum.CLICK_FALLOFF.value - 2 spacing = 1 @@ -425,13 +594,56 @@ def align_ports(self, v_offset=0.0): txt_x = port.x() - txt_width text.setPos(txt_x, port.y() - 1.5) - def draw_node(self): + def _align_ports_vertical(self, v_offset): + # adjust input position + inputs = [p for p in self.inputs if p.isVisible()] + if inputs: + port_width = inputs[0].boundingRect().width() + port_height = inputs[0].boundingRect().height() + half_width = port_width / 2 + delta = self._width / (len(inputs) + 1) + port_x = delta + port_y = (port_height / 2) * -1 + for port in inputs: + port.setPos(port_x - half_width, port_y) + port_x += delta + + # adjust output position + outputs = [p for p in self.outputs if p.isVisible()] + if outputs: + port_width = outputs[0].boundingRect().width() + port_height = outputs[0].boundingRect().height() + half_width = port_width / 2 + delta = self._width / (len(outputs) + 1) + port_x = delta + port_y = self._height - (port_height / 2) + for port in outputs: + port.setPos(port_x - half_width, port_y) + port_x += delta + + def align_ports(self, v_offset=0.0): """ - Re-draw the node item in the scene. - (re-implemented for vertical layout design) + Align input, output ports in the node layout. + + Args: + v_offset (float): port vertical offset. """ + if self.layout_direction is LayoutDirectionEnum.HORIZONTAL.value: + self._align_ports_horizontal(v_offset) + elif self.layout_direction is LayoutDirectionEnum.VERTICAL.value: + self._align_ports_vertical(v_offset) + else: + raise RuntimeError('Node graph layout direction not valid!') + + def _draw_node_horizontal(self): height = self._text_item.boundingRect().height() + 4.0 + # update port text items in visibility. + for port, text in self._input_items.items(): + text.setVisible(port.display_name) + for port, text in self._output_items.items(): + text.setVisible(port.display_name) + # setup initial base size. self._set_base_size(add_h=height) # set text color when node is initialized. @@ -453,6 +665,46 @@ def draw_node(self): self.update() + def _draw_node_vertical(self): + # hide the port text items in vertical layout. + for port, text in self._input_items.items(): + text.setVisible(False) + for port, text in self._output_items.items(): + text.setVisible(False) + + # setup initial base size. + self._set_base_size() + # set text color when node is initialized. + self._set_text_color(self.text_color) + # set the tooltip + self._tooltip_disable(self.disabled) + + # --- setup node layout --- + # (do all the graphic item layout offsets here) + + # align label text + self.align_label(h_offset=6) + # align icon + self.align_icon(h_offset=6, v_offset=4) + # arrange input and output ports. + self.align_ports() + # arrange node widgets + self.align_widgets() + + self.update() + + def draw_node(self): + """ + Re-draw the node item in the scene with proper + calculated size and widgets aligned. + """ + if self.layout_direction is LayoutDirectionEnum.HORIZONTAL.value: + self._draw_node_horizontal() + elif self.layout_direction is LayoutDirectionEnum.VERTICAL.value: + self._draw_node_vertical() + else: + raise RuntimeError('Node graph layout direction not valid!') + def post_init(self, viewer=None, pos=None): """ Called after node has been added into the scene. @@ -540,6 +792,11 @@ def icon(self, path=None): self.update() + @AbstractNodeItem.layout_direction.setter + def layout_direction(self, value=0): + AbstractNodeItem.layout_direction.fset(self, value) + self.draw_node() + @AbstractNodeItem.width.setter def width(self, width=0.0): w, h = self.calc_size() @@ -768,264 +1025,3 @@ def from_dict(self, node_dict): for name, value in widgets.items(): if self._widgets.get(name): self._widgets[name].value = value - - -class NodeItemVertical(NodeItem): - """ - Vertical Node item. - - Args: - name (str): name displayed on the node. - parent (QtWidgets.QGraphicsItem): parent item. - """ - - def __init__(self, name='node', parent=None): - super(NodeItemVertical, self).__init__(name, parent) - font = QtGui.QFont() - font.setPointSize(15) - self.text_item.setFont(font) - - def paint(self, painter, option, widget): - """ - Draws the node base not the ports. - - Args: - painter (QtGui.QPainter): painter used for drawing the item. - option (QtGui.QStyleOptionGraphicsItem): - used to describe the parameters needed to draw. - widget (QtWidgets.QWidget): not used. - """ - self.auto_switch_mode() - - painter.save() - painter.setPen(QtCore.Qt.NoPen) - painter.setBrush(QtCore.Qt.NoBrush) - - # base background. - margin = 1.0 - rect = self.boundingRect() - rect = QtCore.QRectF(rect.left() + margin, - rect.top() + margin, - rect.width() - (margin * 2), - rect.height() - (margin * 2)) - - radius = 4.0 - painter.setBrush(QtGui.QColor(*self.color)) - painter.drawRoundedRect(rect, radius, radius) - - # light overlay on background when selected. - if self.selected: - painter.setBrush( - QtGui.QColor(*NodeEnum.SELECTED_COLOR.value) - ) - painter.drawRoundedRect(rect, radius, radius) - - # top & bottom edge background. - padding = 2.0 - height = 10 - if self.selected: - painter.setBrush(QtGui.QColor(*NodeEnum.SELECTED_COLOR.value)) - else: - painter.setBrush(QtGui.QColor(0, 0, 0, 80)) - for y in [rect.y() + padding, rect.height() - height - 1]: - edge_rect = QtCore.QRectF(rect.x() + padding, y, - rect.width() - (padding * 2), height) - painter.drawRoundedRect(edge_rect, 3.0, 3.0) - - # node border - border_width = 0.8 - border_color = QtGui.QColor(*self.border_color) - if self.selected: - border_width = 1.2 - border_color = QtGui.QColor( - *NodeEnum.SELECTED_BORDER_COLOR.value - ) - border_rect = QtCore.QRectF(rect.left(), rect.top(), - rect.width(), rect.height()) - - pen = QtGui.QPen(border_color, border_width) - pen.setCosmetic(self.viewer().get_zoom() < 0.0) - painter.setBrush(QtCore.Qt.NoBrush) - painter.setPen(pen) - painter.drawRoundedRect(border_rect, radius, radius) - - painter.restore() - - def align_icon(self, h_offset=0.0, v_offset=0.0): - """ - Align node icon to the right side of the node. - - Args: - v_offset (float): vertical offset. - h_offset (float): horizontal offset. - """ - center_y = self.boundingRect().center().y() - icon_rect = self._icon_item.boundingRect() - text_rect = self._text_item.boundingRect() - x = self.boundingRect().right() + h_offset - y = center_y - text_rect.height() - (icon_rect.height() / 2) + v_offset - self._icon_item.setPos(x, y) - - def align_label(self, h_offset=0.0, v_offset=0.0): - """ - Align node label to the right side of the node. - - Args: - v_offset (float): vertical offset. - h_offset (float): horizontal offset. - """ - rect = self._text_item.boundingRect() - x = self.boundingRect().right() + h_offset - y = self.boundingRect().center().y() - (rect.height() / 2) + v_offset - self.text_item.setPos(x, y) - - def align_ports(self, v_offset=0.0): - """ - Align input, output ports in the node layout. - """ - # adjust input position - inputs = [p for p in self.inputs if p.isVisible()] - if inputs: - port_width = inputs[0].boundingRect().width() - port_height = inputs[0].boundingRect().height() - half_width = port_width/2 - delta = self._width / (len(inputs)+1) - port_x = delta - port_y = (port_height / 2) * -1 - for port in inputs: - port.setPos(port_x - half_width, port_y) - port_x += delta - - # adjust output position - outputs = [p for p in self.outputs if p.isVisible()] - if outputs: - port_width = outputs[0].boundingRect().width() - port_height = outputs[0].boundingRect().height() - half_width = port_width / 2 - delta = self._width / (len(outputs)+1) - port_x = delta - port_y = self._height - (port_height / 2) - for port in outputs: - port.setPos(port_x-half_width, port_y) - port_x += delta - - def align_widgets(self, v_offset=0.0): - """ - Align node widgets to the default center of the node. - - Args: - v_offset (float): vertical offset. - """ - if not self._widgets: - return - rect = self.boundingRect() - y = rect.center().y() + v_offset - widget_height = 0.0 - for widget in self._widgets.values(): - widget_rect = widget.boundingRect() - widget_height += widget_rect.height() - y -= widget_height / 2 - - for widget in self._widgets.values(): - widget_rect = widget.boundingRect() - x = rect.center().x() - (widget_rect.width() / 2) - widget.widget().setTitleAlign('center') - widget.setPos(x, y) - y += widget_rect.height() - - def draw_node(self): - """ - Re-draw the node item in the scene. - """ - # setup initial base size. - self._set_base_size() - # set text color when node is initialized. - self._set_text_color(self.text_color) - # set the tooltip - self._tooltip_disable(self.disabled) - - # --- setup node layout --- - # (do all the graphic item layout offsets here) - - # align label text - self.align_label(h_offset=6) - # align icon - self.align_icon(h_offset=6, v_offset=4) - # arrange input and output ports. - self.align_ports() - # arrange node widgets - self.align_widgets() - - self.update() - - def calc_size(self, add_w=0.0, add_h=0.0): - """ - Calculate minimum node size. - - Args: - add_w (float): additional width. - add_h (float): additional height. - """ - p_input_width = 0.0 - p_output_width = 0.0 - p_input_height = 0.0 - p_output_height = 0.0 - for port in self._input_items.keys(): - if port.isVisible(): - p_input_width += port.boundingRect().width() - if not p_input_height: - p_input_height = port.boundingRect().height() - for port in self._output_items.keys(): - if port.isVisible(): - p_output_width += port.boundingRect().width() - if not p_output_height: - p_output_height = port.boundingRect().height() - - widget_width = 0.0 - widget_height = 0.0 - for widget in self._widgets.values(): - if widget.boundingRect().width() > widget_width: - widget_width = widget.boundingRect().width() - widget_height += widget.boundingRect().height() - - width = max([p_input_width, p_output_width, widget_width]) + add_w - height = p_input_height + p_output_height + widget_height + add_h - return width, height - - def add_input(self, name='input', multi_port=False, display_name=True, - locked=False, painter_func=None): - """ - Adds a port qgraphics item into the node with the "port_type" set as - IN_PORT - - Args: - name (str): name for the port. - multi_port (bool): allow multiple connections. - display_name (bool): (not used). - locked (bool): locked state. - painter_func (function): custom paint function. - - Returns: - PortItem: port qgraphics item. - """ - return super(NodeItemVertical, self).add_input( - name, multi_port, False, locked, painter_func) - - def add_output(self, name='output', multi_port=False, display_name=True, - locked=False, painter_func=None): - """ - Adds a port qgraphics item into the node with the "port_type" set as - OUT_PORT - - Args: - name (str): name for the port. - multi_port (bool): allow multiple connections. - display_name (bool): (not used). - locked (bool): locked state. - painter_func (function): custom paint function. - - Returns: - PortItem: port qgraphics item. - """ - return super(NodeItemVertical, self).add_output( - name, multi_port, False, locked, painter_func) diff --git a/NodeGraphQt/qgraphics/node_group.py b/NodeGraphQt/qgraphics/node_group.py index fa4e91dd..5b6a69e0 100644 --- a/NodeGraphQt/qgraphics/node_group.py +++ b/NodeGraphQt/qgraphics/node_group.py @@ -17,18 +17,7 @@ class GroupNodeItem(NodeItem): def __init__(self, name='group', parent=None): super(GroupNodeItem, self).__init__(name, parent) - def paint(self, painter, option, widget): - """ - Draws the node base not the ports or text. - - Args: - painter (QtGui.QPainter): painter used for drawing the item. - option (QtGui.QStyleOptionGraphicsItem): - used to describe the parameters needed to draw. - widget (QtWidgets.QWidget): not used. - """ - self.auto_switch_mode() - + def _paint_horizontal(self, painter, option, widget): painter.save() painter.setBrush(QtCore.Qt.NoBrush) painter.setPen(QtCore.Qt.NoPen) @@ -106,109 +95,7 @@ def paint(self, painter, option, widget): painter.restore() - def align_ports(self, v_offset=0.0): - """ - Align input, output ports in the node layout. - - Args: - v_offset (float): port vertical offset. - """ - width = self._width - txt_offset = PortEnum.CLICK_FALLOFF.value - 2 - spacing = 1 - - # adjust input position - inputs = [p for p in self.inputs if p.isVisible()] - if inputs: - port_width = inputs[0].boundingRect().width() - port_height = inputs[0].boundingRect().height() - port_x = port_width / 2 * -1 - port_x += 3.0 - port_y = v_offset - for port in inputs: - port.setPos(port_x, port_y) - port_y += port_height + spacing - # adjust input text position - for port, text in self._input_items.items(): - if port.isVisible(): - txt_x = port.boundingRect().width() / 2 - txt_offset - txt_x += 3.0 - text.setPos(txt_x, port.y() - 1.5) - - # adjust output position - outputs = [p for p in self.outputs if p.isVisible()] - if outputs: - port_width = outputs[0].boundingRect().width() - port_height = outputs[0].boundingRect().height() - port_x = width - (port_width / 2) - port_x -= 9.0 - port_y = v_offset - for port in outputs: - port.setPos(port_x, port_y) - port_y += port_height + spacing - # adjust output text position - for port, text in self._output_items.items(): - if port.isVisible(): - txt_width = text.boundingRect().width() - txt_offset - txt_x = port.x() - txt_width - text.setPos(txt_x, port.y() - 1.5) - - def draw_node(self): - """ - Re-draw the node item in the scene. - (re-implemented for vertical layout design) - """ - height = self._text_item.boundingRect().height() - - # setup initial base size. - self._set_base_size(add_w=8.0, add_h=height + 10) - # set text color when node is initialized. - self._set_text_color(self.text_color) - # set the tooltip - self._tooltip_disable(self.disabled) - - # --- set the initial node layout --- - # (do all the graphic item layout offsets here) - - # align label text - self.align_label() - # arrange icon - self.align_icon(h_offset=2.0, v_offset=3.0) - # arrange input and output ports. - self.align_ports(v_offset=height) - # arrange node widgets - self.align_widgets(v_offset=height) - - self.update() - - -class GroupNodeVerticalItem(NodeItem): - """ - Vertical Group Node item. - - Args: - name (str): name displayed on the node. - parent (QtWidgets.QGraphicsItem): parent item. - """ - - def __init__(self, name='group', parent=None): - super(GroupNodeVerticalItem, self).__init__(name, parent) - font = QtGui.QFont() - font.setPointSize(15) - self.text_item.setFont(font) - - def paint(self, painter, option, widget): - """ - Draws the node base not the ports or text. - - Args: - painter (QtGui.QPainter): painter used for drawing the item. - option (QtGui.QStyleOptionGraphicsItem): - used to describe the parameters needed to draw. - widget (QtWidgets.QWidget): not used. - """ - self.auto_switch_mode() - + def _paint_vertical(self, painter, option, widget): painter.save() painter.setBrush(QtCore.Qt.NoBrush) painter.setPen(QtCore.Qt.NoPen) @@ -284,38 +171,65 @@ def paint(self, painter, option, widget): painter.drawRect(rect_2) painter.restore() - - def align_icon(self, h_offset=0.0, v_offset=0.0): - """ - Align node icon to the default top left of the node. - - Args: - v_offset (float): vertical offset. - h_offset (float): horizontal offset. - """ + + def _align_icon_horizontal(self, h_offset, v_offset): + super(GroupNodeItem, self)._align_icon_horizontal(h_offset, v_offset) + + def _align_icon_vertical(self, h_offset, v_offset): y = self._height / 2 y -= self._icon_item.boundingRect().height() self._icon_item.setPos(self._width + h_offset, y + v_offset) - def align_label(self, h_offset=0.0, v_offset=0.0): - """ - Center node label text to the top of the node. - - Args: - v_offset (float): vertical offset. - h_offset (float): horizontal offset. - """ + def _align_label_horizontal(self, h_offset, v_offset): + super(GroupNodeItem, self)._align_label_horizontal(h_offset, v_offset) + + def _align_label_vertical(self, h_offset, v_offset): y = self._height / 2 y -= self.text_item.boundingRect().height() / 2 self._text_item.setPos(self._width + h_offset, y + v_offset) - def align_ports(self, v_offset=0.0): - """ - Align input, output ports in the node layout. + def _align_ports_horizontal(self, v_offset): + width = self._width + txt_offset = PortEnum.CLICK_FALLOFF.value - 2 + spacing = 1 - Args: - v_offset (float): port vertical offset. - """ + # adjust input position + inputs = [p for p in self.inputs if p.isVisible()] + if inputs: + port_width = inputs[0].boundingRect().width() + port_height = inputs[0].boundingRect().height() + port_x = port_width / 2 * -1 + port_x += 3.0 + port_y = v_offset + for port in inputs: + port.setPos(port_x, port_y) + port_y += port_height + spacing + # adjust input text position + for port, text in self._input_items.items(): + if port.isVisible(): + txt_x = port.boundingRect().width() / 2 - txt_offset + txt_x += 3.0 + text.setPos(txt_x, port.y() - 1.5) + + # adjust output position + outputs = [p for p in self.outputs if p.isVisible()] + if outputs: + port_width = outputs[0].boundingRect().width() + port_height = outputs[0].boundingRect().height() + port_x = width - (port_width / 2) + port_x -= 9.0 + port_y = v_offset + for port in outputs: + port.setPos(port_x, port_y) + port_y += port_height + spacing + # adjust output text position + for port, text in self._output_items.items(): + if port.isVisible(): + txt_width = text.boundingRect().width() - txt_offset + txt_x = port.x() - txt_width + text.setPos(txt_x, port.y() - 1.5) + + def _align_ports_vertical(self, v_offset): # adjust input position inputs = [p for p in self.inputs if p.isVisible()] if inputs: @@ -342,13 +256,45 @@ def align_ports(self, v_offset=0.0): port.setPos(port_x - half_width, port_y) port_x += delta - def draw_node(self): - """ - Re-draw the node item in the scene. - (re-implemented for vertical layout design) - """ + def _draw_node_horizontal(self): + height = self._text_item.boundingRect().height() + + # update port text items in visibility. + for port, text in self._input_items.items(): + text.setVisible(port.display_name) + for port, text in self._output_items.items(): + text.setVisible(port.display_name) + + # setup initial base size. + self._set_base_size(add_w=8.0, add_h=height + 10) + # set text color when node is initialized. + self._set_text_color(self.text_color) + # set the tooltip + self._tooltip_disable(self.disabled) + + # --- set the initial node layout --- + # (do all the graphic item layout offsets here) + + # align label text + self.align_label() + # arrange icon + self.align_icon(h_offset=2.0, v_offset=3.0) + # arrange input and output ports. + self.align_ports(v_offset=height) + # arrange node widgets + self.align_widgets(v_offset=height) + + self.update() + + def _draw_node_vertical(self): height = self._text_item.boundingRect().height() + # hide the port text items in vertical layout. + for port, text in self._input_items.items(): + text.setVisible(False) + for port, text in self._output_items.items(): + text.setVisible(False) + # setup initial base size. self._set_base_size(add_w=8.0) # set text color when node is initialized. @@ -369,41 +315,3 @@ def draw_node(self): self.align_widgets(v_offset=height / 2) self.update() - - def add_input(self, name='input', multi_port=False, display_name=True, - locked=False, painter_func=None): - """ - Adds a port qgraphics item into the node with the "port_type" set as - IN_PORT - - Args: - name (str): name for the port. - multi_port (bool): allow multiple connections. - display_name (bool): (not used). - locked (bool): locked state. - painter_func (function): custom paint function. - - Returns: - PortItem: port qgraphics item. - """ - return super(GroupNodeVerticalItem, self).add_input( - name, multi_port, False, locked, painter_func) - - def add_output(self, name='output', multi_port=False, display_name=True, - locked=False, painter_func=None): - """ - Adds a port qgraphics item into the node with the "port_type" set as - OUT_PORT - - Args: - name (str): name for the port. - multi_port (bool): allow multiple connections. - display_name (bool): (not used). - locked (bool): locked state. - painter_func (function): custom paint function. - - Returns: - PortItem: port qgraphics item. - """ - return super(GroupNodeVerticalItem, self).add_output( - name, multi_port, False, locked, painter_func) diff --git a/NodeGraphQt/qgraphics/node_port_in.py b/NodeGraphQt/qgraphics/node_port_in.py index e76e5d73..b5c23618 100644 --- a/NodeGraphQt/qgraphics/node_port_in.py +++ b/NodeGraphQt/qgraphics/node_port_in.py @@ -2,7 +2,7 @@ from Qt import QtCore, QtGui, QtWidgets from NodeGraphQt.constants import NodeEnum -from NodeGraphQt.qgraphics.node_base import NodeItem, NodeItemVertical +from NodeGraphQt.qgraphics.node_base import NodeItem class PortInputNodeItem(NodeItem): @@ -25,16 +25,7 @@ def _set_base_size(self, add_w=0.0, add_h=0.0): self._width = width + 60 self._height = height if height >= 60 else 60 - def paint(self, painter, option, widget): - """ - Draws the node base not the ports or text. - - Args: - painter (QtGui.QPainter): painter used for drawing the item. - option (QtGui.QStyleOptionGraphicsItem): - used to describe the parameters needed to draw. - widget (QtWidgets.QWidget): not used. - """ + def _paint_horizontal(self, painter, option, widget): self.auto_switch_mode() painter.save() @@ -94,6 +85,66 @@ def paint(self, painter, option, widget): painter.restore() + def _paint_vertical(self, painter, option, widget): + self.auto_switch_mode() + + painter.save() + painter.setBrush(QtCore.Qt.NoBrush) + painter.setPen(QtCore.Qt.NoPen) + + margin = 2.0 + rect = self.boundingRect() + rect = QtCore.QRectF(rect.left() + margin, + rect.top() + margin, + rect.width() - (margin * 2), + rect.height() - (margin * 2)) + + text_rect = self._text_item.boundingRect() + text_rect = QtCore.QRectF( + rect.center().x() - (text_rect.width() / 2) - 5, + rect.top() + margin, + text_rect.width() + 10, + text_rect.height() + ) + + painter.setBrush(QtGui.QColor(255, 255, 255, 20)) + painter.drawRoundedRect(rect, 20, 20) + + painter.setBrush(QtGui.QColor(0, 0, 0, 100)) + painter.drawRoundedRect(text_rect, 3, 3) + + size = int(rect.height() / 4) + triangle = QtGui.QPolygonF() + triangle.append(QtCore.QPointF(-size, size)) + triangle.append(QtCore.QPointF(0.0, 0.0)) + triangle.append(QtCore.QPointF(size, size)) + + transform = QtGui.QTransform() + transform.translate(rect.center().x(), rect.bottom() - (size / 3)) + transform.rotate(180) + poly = transform.map(triangle) + + if self.selected: + pen = QtGui.QPen( + QtGui.QColor(*NodeEnum.SELECTED_BORDER_COLOR.value), 1.3 + ) + painter.setBrush(QtGui.QColor(*NodeEnum.SELECTED_COLOR.value)) + else: + pen = QtGui.QPen(QtGui.QColor(*self.border_color), 1.2) + painter.setBrush(QtGui.QColor(0, 0, 0, 50)) + + pen.setJoinStyle(QtCore.Qt.MiterJoin) + painter.setPen(pen) + painter.drawPolygon(poly) + + edge_size = 30 + edge_rect = QtCore.QRectF(rect.center().x() - (edge_size / 2), + rect.bottom() - (size * 1.9), + edge_size, 4) + painter.drawRect(edge_rect) + + painter.restore() + def set_proxy_mode(self, mode): """ Set whether to draw the node with proxy mode. @@ -127,21 +178,21 @@ def set_proxy_mode(self, mode): self._text_item.setVisible(visible) - def align_label(self, h_offset=0.0, v_offset=0.0): - """ - Center node label text to the top of the node. - - Args: - v_offset (float): vertical offset. - h_offset (float): horizontal offset. - """ + def _align_label_horizontal(self, h_offset, v_offset): rect = self.boundingRect() text_rect = self._text_item.boundingRect() x = rect.center().x() - (text_rect.width() / 2) y = rect.center().y() - (text_rect.height() / 2) self._text_item.setPos(x + h_offset, y + v_offset) - def align_ports(self, v_offset=0.0): + def _align_label_vertical(self, h_offset, v_offset): + rect = self.boundingRect() + text_rect = self._text_item.boundingRect() + x = rect.center().x() - (text_rect.width() / 1.5) - 2.0 + y = rect.center().y() - text_rect.height() - 2.0 + self._text_item.setPos(x + h_offset, y + v_offset) + + def _align_ports_horizontal(self, v_offset): """ Align input, output ports in the node layout. """ @@ -151,9 +202,12 @@ def align_ports(self, v_offset=0.0): if ports: v_offset -= ports[0].boundingRect().height() / 2 break - super(PortInputNodeItem, self).align_ports(v_offset=v_offset) + super(PortInputNodeItem, self)._align_ports_horizontal(v_offset) - def draw_node(self): + def _align_ports_vertical(self, v_offset): + super(PortInputNodeItem, self)._align_ports_vertical(v_offset) + + def _draw_node_horizontal(self): """ Re-draw the node item in the scene. (re-implemented for vertical layout design) @@ -178,86 +232,3 @@ def draw_node(self): self.align_widgets() self.update() - - -class PortInputNodeVerticalItem(PortInputNodeItem): - - def paint(self, painter, option, widget): - self.auto_switch_mode() - - painter.save() - painter.setBrush(QtCore.Qt.NoBrush) - painter.setPen(QtCore.Qt.NoPen) - - margin = 2.0 - rect = self.boundingRect() - rect = QtCore.QRectF(rect.left() + margin, - rect.top() + margin, - rect.width() - (margin * 2), - rect.height() - (margin * 2)) - - text_rect = self._text_item.boundingRect() - text_rect = QtCore.QRectF( - rect.center().x() - (text_rect.width() / 2) - 5, - rect.top() + margin, - text_rect.width() + 10, - text_rect.height() - ) - - painter.setBrush(QtGui.QColor(255, 255, 255, 20)) - painter.drawRoundedRect(rect, 20, 20) - - painter.setBrush(QtGui.QColor(0, 0, 0, 100)) - painter.drawRoundedRect(text_rect, 3, 3) - - size = int(rect.height() / 4) - triangle = QtGui.QPolygonF() - triangle.append(QtCore.QPointF(-size, size)) - triangle.append(QtCore.QPointF(0.0, 0.0)) - triangle.append(QtCore.QPointF(size, size)) - - transform = QtGui.QTransform() - transform.translate(rect.center().x(), rect.bottom() - (size / 3)) - transform.rotate(180) - poly = transform.map(triangle) - - if self.selected: - pen = QtGui.QPen( - QtGui.QColor(*NodeEnum.SELECTED_BORDER_COLOR.value), 1.3 - ) - painter.setBrush(QtGui.QColor(*NodeEnum.SELECTED_COLOR.value)) - else: - pen = QtGui.QPen(QtGui.QColor(*self.border_color), 1.2) - painter.setBrush(QtGui.QColor(0, 0, 0, 50)) - - pen.setJoinStyle(QtCore.Qt.MiterJoin) - painter.setPen(pen) - painter.drawPolygon(poly) - - edge_size = 30 - edge_rect = QtCore.QRectF(rect.center().x() - (edge_size / 2), - rect.bottom() - (size * 1.9), - edge_size, 4) - painter.drawRect(edge_rect) - - painter.restore() - - def align_label(self, h_offset=0.0, v_offset=0.0): - """ - Center node label text to the center of the node. - - Args: - v_offset (float): vertical offset. - h_offset (float): horizontal offset. - """ - rect = self.boundingRect() - text_rect = self._text_item.boundingRect() - x = rect.center().x() - (text_rect.width() / 2) - y = rect.center().y() - text_rect.height() - 2.0 - self._text_item.setPos(x + h_offset, y + v_offset) - - def align_ports(self, v_offset=0.0): - """ - Align input, output ports in the node layout. - """ - NodeItemVertical.align_ports(self, v_offset=v_offset) diff --git a/NodeGraphQt/qgraphics/node_port_out.py b/NodeGraphQt/qgraphics/node_port_out.py index a692c74a..733b0043 100644 --- a/NodeGraphQt/qgraphics/node_port_out.py +++ b/NodeGraphQt/qgraphics/node_port_out.py @@ -2,7 +2,7 @@ from Qt import QtCore, QtGui, QtWidgets from NodeGraphQt.constants import NodeEnum -from NodeGraphQt.qgraphics.node_base import NodeItem, NodeItemVertical +from NodeGraphQt.qgraphics.node_base import NodeItem class PortOutputNodeItem(NodeItem): @@ -25,16 +25,7 @@ def _set_base_size(self, add_w=0.0, add_h=0.0): self._width = width + 60 self._height = height if height >= 60 else 60 - def paint(self, painter, option, widget): - """ - Draws the node base not the ports or text. - - Args: - painter (QtGui.QPainter): painter used for drawing the item. - option (QtGui.QStyleOptionGraphicsItem): - used to describe the parameters needed to draw. - widget (QtWidgets.QWidget): not used. - """ + def _paint_horizontal(self, painter, option, widget): self.auto_switch_mode() painter.save() @@ -94,6 +85,66 @@ def paint(self, painter, option, widget): painter.restore() + def _paint_vertical(self, painter, option, widget): + self.auto_switch_mode() + + painter.save() + painter.setBrush(QtCore.Qt.NoBrush) + painter.setPen(QtCore.Qt.NoPen) + + margin = 2.0 + rect = self.boundingRect() + rect = QtCore.QRectF(rect.left() + margin, + rect.top() + margin, + rect.width() - (margin * 2), + rect.height() - (margin * 2)) + + text_rect = self._text_item.boundingRect() + text_rect = QtCore.QRectF( + rect.center().x() - (text_rect.width() / 2) - 5, + rect.height() - text_rect.height(), + text_rect.width() + 10, + text_rect.height() + ) + + painter.setBrush(QtGui.QColor(255, 255, 255, 20)) + painter.drawRoundedRect(rect, 20, 20) + + painter.setBrush(QtGui.QColor(0, 0, 0, 100)) + painter.drawRoundedRect(text_rect, 3, 3) + + size = int(rect.height() / 4) + triangle = QtGui.QPolygonF() + triangle.append(QtCore.QPointF(-size, size)) + triangle.append(QtCore.QPointF(0.0, 0.0)) + triangle.append(QtCore.QPointF(size, size)) + + transform = QtGui.QTransform() + transform.translate(rect.center().x(), rect.y() + (size / 3)) + # transform.rotate(-90) + poly = transform.map(triangle) + + if self.selected: + pen = QtGui.QPen( + QtGui.QColor(*NodeEnum.SELECTED_BORDER_COLOR.value), 1.3 + ) + painter.setBrush(QtGui.QColor(*NodeEnum.SELECTED_COLOR.value)) + else: + pen = QtGui.QPen(QtGui.QColor(*self.border_color), 1.2) + painter.setBrush(QtGui.QColor(0, 0, 0, 50)) + + pen.setJoinStyle(QtCore.Qt.MiterJoin) + painter.setPen(pen) + painter.drawPolygon(poly) + + edge_size = 30 + edge_rect = QtCore.QRectF(rect.center().x() - (edge_size / 2), + rect.y() + (size * 1.6), + edge_size, 4) + painter.drawRect(edge_rect) + + painter.restore() + def set_proxy_mode(self, mode): """ Set whether to draw the node with proxy mode. @@ -127,21 +178,21 @@ def set_proxy_mode(self, mode): self._text_item.setVisible(visible) - def align_label(self, h_offset=0.0, v_offset=0.0): - """ - Center node label text to the center of the node. - - Args: - v_offset (float): vertical offset. - h_offset (float): horizontal offset. - """ + def _align_label_horizontal(self, h_offset, v_offset): rect = self.boundingRect() text_rect = self._text_item.boundingRect() x = rect.center().x() - (text_rect.width() / 2) y = rect.center().y() - (text_rect.height() / 2) self._text_item.setPos(x + h_offset, y + v_offset) - def align_ports(self, v_offset=0.0): + def _align_label_vertical(self, h_offset, v_offset): + rect = self.boundingRect() + text_rect = self._text_item.boundingRect() + x = rect.center().x() - (text_rect.width() / 1.5) - 2.0 + y = rect.height() - text_rect.height() - 4.0 + self._text_item.setPos(x + h_offset, y + v_offset) + + def _align_ports_horizontal(self, v_offset): """ Align input, output ports in the node layout. """ @@ -151,9 +202,12 @@ def align_ports(self, v_offset=0.0): if ports: v_offset -= ports[0].boundingRect().height() / 2 break - super(PortOutputNodeItem, self).align_ports(v_offset=v_offset) + super(PortOutputNodeItem, self)._align_ports_horizontal(v_offset) - def draw_node(self): + def _align_ports_vertical(self, v_offset): + super(PortOutputNodeItem, self)._align_ports_vertical(v_offset) + + def _draw_node_horizontal(self): """ Re-draw the node item in the scene. (re-implemented for vertical layout design) @@ -178,95 +232,3 @@ def draw_node(self): self.align_widgets() self.update() - - -class PortOutputNodeVerticalItem(PortOutputNodeItem): - - def paint(self, painter, option, widget): - """ - Draws the node base not the ports or text. - - Args: - painter (QtGui.QPainter): painter used for drawing the item. - option (QtGui.QStyleOptionGraphicsItem): - used to describe the parameters needed to draw. - widget (QtWidgets.QWidget): not used. - """ - self.auto_switch_mode() - - painter.save() - painter.setBrush(QtCore.Qt.NoBrush) - painter.setPen(QtCore.Qt.NoPen) - - margin = 2.0 - rect = self.boundingRect() - rect = QtCore.QRectF(rect.left() + margin, - rect.top() + margin, - rect.width() - (margin * 2), - rect.height() - (margin * 2)) - - text_rect = self._text_item.boundingRect() - text_rect = QtCore.QRectF( - rect.center().x() - (text_rect.width() / 2) - 5, - rect.height() - text_rect.height(), - text_rect.width() + 10, - text_rect.height() - ) - - painter.setBrush(QtGui.QColor(255, 255, 255, 20)) - painter.drawRoundedRect(rect, 20, 20) - - painter.setBrush(QtGui.QColor(0, 0, 0, 100)) - painter.drawRoundedRect(text_rect, 3, 3) - - size = int(rect.height() / 4) - triangle = QtGui.QPolygonF() - triangle.append(QtCore.QPointF(-size, size)) - triangle.append(QtCore.QPointF(0.0, 0.0)) - triangle.append(QtCore.QPointF(size, size)) - - transform = QtGui.QTransform() - transform.translate(rect.center().x(), rect.y() + (size / 3)) - # transform.rotate(-90) - poly = transform.map(triangle) - - if self.selected: - pen = QtGui.QPen( - QtGui.QColor(*NodeEnum.SELECTED_BORDER_COLOR.value), 1.3 - ) - painter.setBrush(QtGui.QColor(*NodeEnum.SELECTED_COLOR.value)) - else: - pen = QtGui.QPen(QtGui.QColor(*self.border_color), 1.2) - painter.setBrush(QtGui.QColor(0, 0, 0, 50)) - - pen.setJoinStyle(QtCore.Qt.MiterJoin) - painter.setPen(pen) - painter.drawPolygon(poly) - - edge_size = 30 - edge_rect = QtCore.QRectF(rect.center().x() - (edge_size / 2), - rect.y() + (size * 1.6), - edge_size, 4) - painter.drawRect(edge_rect) - - painter.restore() - - def align_label(self, h_offset=0.0, v_offset=0.0): - """ - Center node label text to the center of the node. - - Args: - v_offset (float): vertical offset. - h_offset (float): horizontal offset. - """ - rect = self.boundingRect() - text_rect = self._text_item.boundingRect() - x = rect.center().x() - (text_rect.width() / 2) - y = rect.height() - text_rect.height() - 4.0 - self._text_item.setPos(x + h_offset, y + v_offset) - - def align_ports(self, v_offset=0.0): - """ - Align input, output ports in the node layout. - """ - NodeItemVertical.align_ports(self, v_offset=v_offset) diff --git a/NodeGraphQt/qgraphics/pipe.py b/NodeGraphQt/qgraphics/pipe.py index 29beada0..2795210b 100644 --- a/NodeGraphQt/qgraphics/pipe.py +++ b/NodeGraphQt/qgraphics/pipe.py @@ -4,11 +4,13 @@ from Qt import QtCore, QtGui, QtWidgets from NodeGraphQt.constants import ( - PipeEnum, PipeLayoutEnum, PortTypeEnum, Z_VAL_PIPE, - Z_VAL_NODE_WIDGET, + LayoutDirectionEnum, + PipeEnum, + PipeLayoutEnum, + PortTypeEnum, ITEM_CACHE_MODE, - NODE_LAYOUT_VERTICAL, NODE_LAYOUT_HORIZONTAL, - NODE_LAYOUT_DIRECTION + Z_VAL_PIPE, + Z_VAL_NODE_WIDGET ) from NodeGraphQt.qgraphics.port import PortItem @@ -261,11 +263,11 @@ def draw_path(self, start_port, end_port=None, cursor_pos=None): path.lineTo(pos2) self.setPath(path) return - else: - if NODE_LAYOUT_DIRECTION is NODE_LAYOUT_VERTICAL: - self.__draw_path_vertical(start_port, pos1, pos2, path) - elif NODE_LAYOUT_DIRECTION is NODE_LAYOUT_HORIZONTAL: - self.__draw_path_horizontal(start_port, pos1, pos2, path) + + if self.viewer_layout_direction() is LayoutDirectionEnum.VERTICAL.value: + self.__draw_path_vertical(start_port, pos1, pos2, path) + elif self.viewer_layout_direction() is LayoutDirectionEnum.HORIZONTAL.value: + self.__draw_path_horizontal(start_port, pos1, pos2, path) def reset_path(self): path = QtGui.QPainterPath(QtCore.QPointF(0.0, 0.0)) @@ -293,6 +295,11 @@ def viewer_pipe_layout(self): viewer = self.scene().viewer() return viewer.get_pipe_layout() + def viewer_layout_direction(self): + if self.scene(): + viewer = self.scene().viewer() + return viewer.get_layout_direction() + def activate(self): self._active = True color = QtGui.QColor(*PipeEnum.ACTIVE_COLOR.value) diff --git a/NodeGraphQt/widgets/viewer.py b/NodeGraphQt/widgets/viewer.py index 6fc3e744..5e899145 100644 --- a/NodeGraphQt/widgets/viewer.py +++ b/NodeGraphQt/widgets/viewer.py @@ -6,7 +6,9 @@ from Qt import QtGui, QtCore, QtWidgets from NodeGraphQt.base.menu import BaseMenu -from NodeGraphQt.constants import PortTypeEnum, PipeLayoutEnum +from NodeGraphQt.constants import ( + LayoutDirectionEnum, PortTypeEnum, PipeLayoutEnum +) from NodeGraphQt.qgraphics.node_abstract import AbstractNodeItem from NodeGraphQt.qgraphics.node_backdrop import BackdropNodeItem from NodeGraphQt.qgraphics.pipe import PipeItem, LivePipeItem @@ -71,6 +73,8 @@ def __init__(self, parent=None, undo_stack=None): self._update_scene() self._last_size = self.size() + self._layout_direction = LayoutDirectionEnum.HORIZONTAL.value + self._pipe_layout = PipeLayoutEnum.CURVED.value self._detached_port = None self._start_port = None @@ -999,7 +1003,7 @@ def all_pipes(self): Returns all pipe qgraphic items. Returns: - list[Pipe]: instances of pipe items. + list[PipeItem]: instances of pipe items. """ excl = [self._LIVE_PIPE, self._SLICER_PIPE] return [i for i in self.scene().items() @@ -1153,12 +1157,34 @@ def set_pipe_layout(self, layout): Sets the pipe layout mode and redraw all pipe items in the scene. Args: - layout (int): pipe layout mode. (see the contants module) + layout (int): pipe layout mode. (see the constants module) """ self._pipe_layout = layout for pipe in self.all_pipes(): pipe.draw_path(pipe.input_port, pipe.output_port) + def get_layout_direction(self): + """ + Returns the layout direction set on the the node graph viewer + used by the pipe items for drawing. + + Returns: + int: graph layout mode. + """ + return self._layout_direction + + def set_layout_direction(self, direction): + """ + Sets the node graph viewer layout direction for re-drawing + the pipe items. + + Args: + direction (int): graph layout direction. + """ + self._layout_direction = direction + for pipe_item in self.all_pipes(): + pipe_item.draw_path(pipe_item.input_port, pipe_item.output_port) + def reset_zoom(self, cent=None): """ Reset the viewer zoom level. diff --git a/docs/_images/layout_direction_switch.gif b/docs/_images/layout_direction_switch.gif new file mode 100644 index 00000000..f7a338fe Binary files /dev/null and b/docs/_images/layout_direction_switch.gif differ diff --git a/docs/nodes/BaseNode.rst b/docs/nodes/BaseNode.rst index 7d23ea5e..6d3fcc2b 100644 --- a/docs/nodes/BaseNode.rst +++ b/docs/nodes/BaseNode.rst @@ -3,4 +3,4 @@ BaseNode .. autoclass:: NodeGraphQt.BaseNode :members: - :exclude-members: update_model + :exclude-members: update_model, set_layout_direction