Source code for dearpygui_obj.node

from __future__ import annotations

from enum import Enum
from warnings import warn
from typing import TYPE_CHECKING, NamedTuple, overload

from dearpygui import dearpygui as dpgcore

from dearpygui_obj import _register_item_type, try_get_item_by_id, wrap_callback
from dearpygui_obj.wrapper.widget import Widget, ItemWidgetMx, ContainerWidgetMx, ConfigProperty

if TYPE_CHECKING:
    from typing import Optional, Iterable, Callable
    from dearpygui_obj import PyGuiCallback





## While I personally think it is better design to raise an exception here than return None,
## (so that the user can expect they will always have a NodeLink after a successful call to add_link)
## it doesn't seem appropriate to raise an exception for an operation that does not raise an exception
## in DPG. So lets generate warnings instead so at least the user can tell what went wrong.
def _get_link(end1: NodeAttribute, end2: NodeAttribute) -> Optional[NodeLink]:
    endpoints = end1, end2
    input, output = None, None
    for end in endpoints:
        if end.is_input():
            if input is not None:
                warn('attempt to link two node inputs')
                return None
            input = end
        if end.is_output():
            if output is not None:
                warn('attempt to link two node outputs')
                return None
            output = end
    if input is None:
        warn('did not provide a node input')
        return None
    if output is None:
        warn('did not provide a node output')
        return None

    return NodeLink(input=input, output=output)

def _get_link_from_ids(id1: str, id2: str) -> Optional[NodeLink]:
    end1 = try_get_item_by_id(id1)
    end2 = try_get_item_by_id(id2)
    if not isinstance(end1, NodeAttribute) or not isinstance(end2, NodeAttribute):
        warn('item ID does not reference a node attribute')
        return None
    return _get_link(end1, end2)

[docs]@_register_item_type('mvAppItemType::NodeEditor') class NodeEditor(Widget, ItemWidgetMx, ContainerWidgetMx['NodeEditor']): """A canvas specific to graph node workflow. Should only contain :class:`.Node` objects. Any other kind of widget will not be displayed. """ def __init__(self, **config): super().__init__(**config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_node_editor( self.id, link_callback=self._on_link, delink_callback=self._on_delink, **dpg_args, ) ## Links @overload def delete_link(self, link: NodeLink) -> None: ... @overload def delete_link(self, end1: NodeAttribute, end2: NodeAttribute) -> None: ... ## Node and Link Selection
[docs] def get_selected_nodes(self) -> Iterable[Node]: """Get all nodes in the selected state.""" for node_id in dpgcore.get_selected_nodes(self.id): node = try_get_item_by_id(node_id) if node is not None: yield node
[docs] def clear_node_selection(self) -> None: """Clears all nodes from being in the selection state.""" dpgcore.clear_selected_nodes(self.id)
## Callbacks ## workaround for the fact that you can't set the link_callback or delink_callback properties in DPG _on_link_callback: Optional[Callable] = None _on_delink_callback: Optional[Callable] = None def _on_link(self, sender, data) -> None: if self._on_link_callback is not None: self._on_link_callback(sender, data) def _on_delink(self, sender, data) -> None: if self._on_delink_callback is not None: self._on_delink_callback(sender, data)
[docs]@_register_item_type('mvAppItemType::Node') class Node(Widget, ItemWidgetMx, ContainerWidgetMx['Node']): """A :class:`.NodeEditor` node. Should only contain :class:`.NodeAttribute` objects, any other kind of widget will not be displayed. Note that :class:`.NodeAttribute` objects may contain any kind or number of widget though.""" label: str = ConfigProperty() draggable: bool = ConfigProperty() def __init__(self, label: str = None, **config): super().__init__(label=label, **config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_node(self.id, **dpg_args)
[docs]class NodeAttributeType(Enum): """Specifies how a :class:`.NodeAttribute` will link to other nodes.""" Input = None #: Input nodes may only link to Output nodes. Output = 'output' #: Output nodes may only link to Input nodes. Static = 'static' #: Static nodes do not link. They are still useful as containers to place widgets inside a node.
[docs]def input_attribute(*, id: Optional[int] = None) -> NodeAttribute: """Shortcut constructor for ``NodeAttribute(NodeAttributeType.Input)``""" return NodeAttribute(NodeAttributeType.Input, id=id)
[docs]def output_attribute(*, id: Optional[int] = None) -> NodeAttribute: """Shortcut constructor for ``NodeAttribute(NodeAttributeType.Output)``""" return NodeAttribute(NodeAttributeType.Output, id=id)
[docs]def static_attribute(*, id: Optional[int] = None) -> NodeAttribute: """Shortcut constructor for ``NodeAttribute(NodeAttributeType.Static)``""" return NodeAttribute(NodeAttributeType.Static, id=id)
[docs]@_register_item_type('mvAppItemType::NodeAttribute') class NodeAttribute(Widget, ItemWidgetMx, ContainerWidgetMx['NodeAttribute']): """An attachment point for a :class:`.Node`.""" type: NodeAttributeType @ConfigProperty() def type(self) -> NodeAttributeType: config = self.get_config() for mode in NodeAttributeType: if mode.value is not None and config.get(mode.value): return mode return NodeAttributeType.Input @type.getconfig def type(self, value: NodeAttributeType): return { mode.value : (mode == value) for mode in NodeAttributeType if mode.value is not None } def __init__(self, type: NodeAttributeType = NodeAttributeType.Input, **config): super().__init__(type=type, **config) def __setup_add_widget__(self, dpg_args) -> None: dpgcore.add_node_attribute(self.id, **dpg_args)
[docs] def is_input(self) -> bool: """Shortcut for ``self.type == NodeAttributeType.Input``.""" return self.type == NodeAttributeType.Input
[docs] def is_output(self) -> bool: """Shortcut for ``self.type == NodeAttributeType.Output``.""" return self.type == NodeAttributeType.Output
[docs] def is_static(self) -> bool: """Shortcut for ``self.type == NodeAttributeType.Static``.""" return self.type == NodeAttributeType.Static
__all__ = [ 'NodeEditor', 'Node', 'NodeAttribute', 'NodeLink', 'NodeAttributeType', 'input_attribute', 'output_attribute', 'static_attribute', ]