haybale-graph-editor 0.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- haybale_graph_editor-0.0.2/.gitignore +205 -0
- haybale_graph_editor-0.0.2/PKG-INFO +8 -0
- haybale_graph_editor-0.0.2/README.md +9 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/__init__.py +59 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/__init__.py +0 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/__init__.py +16 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/connection_info_popup.py +134 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/event_handlers.py +46 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/graph_canvas_manager.py +217 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/__init__.py +11 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/context_menu.py +543 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/context_menu_actions.py +58 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/interaction.py +56 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/selection.py +147 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/handlers/visual_layer.py +465 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/node_menu_builder.py +389 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/ui_edge.py +205 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_canvas/ui_node.py +300 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/graph_editor.py +515 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/editors/node_status.py +66 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/focuses.py +70 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/create_node_panel.py +99 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/edge_actions.py +54 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/node_actions.py +171 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/node_errors.py +87 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/port_info.py +53 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/context_menu/selection_actions.py +80 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/edge_panels.py +283 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/graph_info_panel.py +52 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/node_ports_panel.py +80 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/node_props_panel.py +83 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/panels/node_settings.py +71 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/protocols.py +54 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/state/__init__.py +0 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/state/edit_state.py +57 -0
- haybale_graph_editor-0.0.2/haybale_graph_editor/state/graph_app_state.py +69 -0
- haybale_graph_editor-0.0.2/pyproject.toml +27 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
|
|
110
|
+
# pdm
|
|
111
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
112
|
+
#pdm.lock
|
|
113
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
114
|
+
# in version control.
|
|
115
|
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
|
116
|
+
.pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
121
|
+
__pypackages__/
|
|
122
|
+
|
|
123
|
+
# Celery stuff
|
|
124
|
+
celerybeat-schedule
|
|
125
|
+
celerybeat.pid
|
|
126
|
+
|
|
127
|
+
# SageMath parsed files
|
|
128
|
+
*.sage.py
|
|
129
|
+
|
|
130
|
+
# Environments
|
|
131
|
+
**/.env
|
|
132
|
+
.venv
|
|
133
|
+
env/
|
|
134
|
+
venv/
|
|
135
|
+
ENV/
|
|
136
|
+
env.bak/
|
|
137
|
+
venv.bak/
|
|
138
|
+
|
|
139
|
+
# Spyder project settings
|
|
140
|
+
.spyderproject
|
|
141
|
+
.spyproject
|
|
142
|
+
|
|
143
|
+
# Rope project settings
|
|
144
|
+
.ropeproject
|
|
145
|
+
|
|
146
|
+
# mkdocs documentation
|
|
147
|
+
/site
|
|
148
|
+
|
|
149
|
+
# mypy
|
|
150
|
+
.mypy_cache/
|
|
151
|
+
.dmypy.json
|
|
152
|
+
dmypy.json
|
|
153
|
+
|
|
154
|
+
# Pyre type checker
|
|
155
|
+
.pyre/
|
|
156
|
+
|
|
157
|
+
# pytype static type analyzer
|
|
158
|
+
.pytype/
|
|
159
|
+
|
|
160
|
+
# Cython debug symbols
|
|
161
|
+
cython_debug/
|
|
162
|
+
|
|
163
|
+
# PyCharm
|
|
164
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
165
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
166
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
167
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
168
|
+
.idea/
|
|
169
|
+
|
|
170
|
+
# Abstra
|
|
171
|
+
# Abstra is an AI-powered process automation framework.
|
|
172
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
173
|
+
# Learn more at https://abstra.io/docs
|
|
174
|
+
.abstra/
|
|
175
|
+
|
|
176
|
+
# Visual Studio Code
|
|
177
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
178
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
179
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
180
|
+
# you could uncomment the following to ignore the enitre vscode folder
|
|
181
|
+
.vscode/
|
|
182
|
+
|
|
183
|
+
# Ruff stuff:
|
|
184
|
+
.ruff_cache/
|
|
185
|
+
|
|
186
|
+
# PyPI configuration file
|
|
187
|
+
.pypirc
|
|
188
|
+
|
|
189
|
+
# Cursor
|
|
190
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
191
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
192
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
193
|
+
.cursorignore
|
|
194
|
+
.cursorindexingignore
|
|
195
|
+
|
|
196
|
+
.DS_Store
|
|
197
|
+
saves/*.*
|
|
198
|
+
|
|
199
|
+
# Dev-mode graphs (local testing only, not part of the repo)
|
|
200
|
+
graphs/*.json
|
|
201
|
+
# MkDocs build output
|
|
202
|
+
site/
|
|
203
|
+
|
|
204
|
+
# Local git worktrees (claude-code workflow)
|
|
205
|
+
.worktrees/
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# haybale-graph-editor
|
|
2
|
+
|
|
3
|
+
Visual graph editor library for Haywire. Provides:
|
|
4
|
+
|
|
5
|
+
- `GraphContainer` (Protocol) — what a graph-source library must implement
|
|
6
|
+
- `GraphAppState` (AppState) — shared registry of open graph containers, keyed by `binding_id`
|
|
7
|
+
- `GraphEditor` (BaseEditor) — the canvas-hosting editor surface
|
|
8
|
+
|
|
9
|
+
Source libraries (e.g. `haybale-haystack`) register containers; this library renders them.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""haybale-graph-editor: graph editor library for Haywire.
|
|
2
|
+
|
|
3
|
+
Provides the GraphContainer protocol, GraphAppState registry, and
|
|
4
|
+
GraphEditor surface. Decoupled from any specific graph source — source
|
|
5
|
+
libraries register their containers, this library renders them.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from importlib.metadata import version as _pkg_version
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from haywire.core.library.base import BaseLibrary
|
|
12
|
+
from haywire.core.library.decorator import library
|
|
13
|
+
from haywire.core.state import LibraryStateRegistry
|
|
14
|
+
from haywire.ui.editor.registry import EditorTypeRegistry
|
|
15
|
+
|
|
16
|
+
# Public API re-exports
|
|
17
|
+
from haybale_graph_editor.protocols import GraphContainer
|
|
18
|
+
from haybale_graph_editor.state.graph_app_state import GraphAppState
|
|
19
|
+
from haywire.ui.panel.registry import PanelRegistry
|
|
20
|
+
|
|
21
|
+
__all__ = ["GraphContainer", "GraphAppState", "Library"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@library(
|
|
25
|
+
label="Graph Editor",
|
|
26
|
+
id="graph_editor",
|
|
27
|
+
version=_pkg_version("haybale-graph-editor"),
|
|
28
|
+
description="Visual graph editor library — host-agnostic",
|
|
29
|
+
url="",
|
|
30
|
+
help_url="",
|
|
31
|
+
author="",
|
|
32
|
+
author_url="",
|
|
33
|
+
dependencies=[],
|
|
34
|
+
tags=["graph-editor"],
|
|
35
|
+
file_watcher=True,
|
|
36
|
+
)
|
|
37
|
+
class Library(BaseLibrary):
|
|
38
|
+
"""Graph Editor library."""
|
|
39
|
+
|
|
40
|
+
def register_components(self):
|
|
41
|
+
base_path = Path(__file__).parent
|
|
42
|
+
|
|
43
|
+
self.add_folder_to_registry(
|
|
44
|
+
folder_path=str(base_path / "state"),
|
|
45
|
+
registry_cls=LibraryStateRegistry,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
self.add_folder_to_registry(
|
|
49
|
+
folder_path=str(base_path / "panels"),
|
|
50
|
+
registry_cls=PanelRegistry,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
self.add_folder_to_registry(
|
|
54
|
+
folder_path=str(base_path / "editors"),
|
|
55
|
+
registry_cls=EditorTypeRegistry,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def validate(self) -> bool:
|
|
59
|
+
return True
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .connection_info_popup import EdgeInfoPopup
|
|
2
|
+
from .graph_canvas_manager import GraphCanvasManager
|
|
3
|
+
from .node_menu_builder import NodeMenuBuilder
|
|
4
|
+
from .ui_edge import EdgeVisualState
|
|
5
|
+
from .ui_edge import UIEdge
|
|
6
|
+
from .ui_node import UINode
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"EdgeInfoPopup",
|
|
10
|
+
"EdgeVisualState",
|
|
11
|
+
"GraphCanvasManager",
|
|
12
|
+
"GraphEventMetadata",
|
|
13
|
+
"NodeMenuBuilder",
|
|
14
|
+
"UIEdge",
|
|
15
|
+
"UINode",
|
|
16
|
+
]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ConnectionInfoPopup - Detailed connection information display component
|
|
3
|
+
|
|
4
|
+
This component provides a dedicated popup for inspecting edge/connection details including:
|
|
5
|
+
- Connection path (nodes and ports)
|
|
6
|
+
- Validation status
|
|
7
|
+
- Error details with full error rendering
|
|
8
|
+
- Warning messages
|
|
9
|
+
- Adapter chain visualization and testing
|
|
10
|
+
- Execution statistics
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from nicegui import ui
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from haywire.core.edge.edge import Edge
|
|
17
|
+
from haywire.core.errors.haywire_exception import HaywireException
|
|
18
|
+
from haywire.core.edge.edge_wrapper import EdgeWrapperState
|
|
19
|
+
|
|
20
|
+
from haywire.ui import elements as hui
|
|
21
|
+
from haywire.ui.errors.error_info import error_render_detail
|
|
22
|
+
|
|
23
|
+
from haywire.ui.components.popup import Popup
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EdgeInfoPopup:
|
|
27
|
+
"""Dedicated popup for displaying detailed connection/edge information."""
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self._info_popup: Optional[Popup] = None
|
|
31
|
+
|
|
32
|
+
def show(self, x: float, y: float, edge_id: str, edge: Edge, state: EdgeWrapperState):
|
|
33
|
+
"""Show detailed connection information in a dedicated popup."""
|
|
34
|
+
# Close any existing popup first
|
|
35
|
+
self.close()
|
|
36
|
+
|
|
37
|
+
# Create a larger popup for detailed information
|
|
38
|
+
popup = Popup.create_context_menu(
|
|
39
|
+
"Connection Details", x + 10, y + 10, width="400px", clamp_to_viewport=False
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
with popup:
|
|
43
|
+
with ui.column().classes("w-full gap-2 p-2"):
|
|
44
|
+
# Header with connection status
|
|
45
|
+
with ui.row().classes("w-full"):
|
|
46
|
+
ui.label(f"{edge.edge_type}").classes("text-xs hw-text-muted ml-2 mt-2")
|
|
47
|
+
is_valid = state.is_valid()
|
|
48
|
+
status_icon = "✓" if is_valid else "✗"
|
|
49
|
+
status_color = "hw-text-success" if is_valid else "hw-text-danger"
|
|
50
|
+
status_text = "Valid" if is_valid else "Invalid"
|
|
51
|
+
ui.label(f"{status_icon} {status_text}").classes(f"text-sm font-bold {status_color}")
|
|
52
|
+
|
|
53
|
+
ui.separator().classes("my-2")
|
|
54
|
+
|
|
55
|
+
# Error Section (if present, expandable, default open)
|
|
56
|
+
error = state.get_error()
|
|
57
|
+
if error and isinstance(error, HaywireException):
|
|
58
|
+
with hui.expansion_section("Error Details"):
|
|
59
|
+
with (
|
|
60
|
+
ui.card()
|
|
61
|
+
.classes("w-full p-3")
|
|
62
|
+
.style("background: var(--hw-danger-bg); border: 1px solid var(--hw-danger);")
|
|
63
|
+
):
|
|
64
|
+
ui.label(f"Category: {error.category}").classes("text-xs hw-text-danger ml-2")
|
|
65
|
+
# Render the error detail with button to show full details
|
|
66
|
+
error_render_detail(error)
|
|
67
|
+
|
|
68
|
+
# Warning Section (if present, expandable, default open)
|
|
69
|
+
if state.has_warning():
|
|
70
|
+
with hui.expansion_section("Warning"):
|
|
71
|
+
with (
|
|
72
|
+
ui.card()
|
|
73
|
+
.classes("w-full p-3")
|
|
74
|
+
.style("background: var(--hw-bg-surface); border: 1px solid var(--hw-border);")
|
|
75
|
+
):
|
|
76
|
+
with ui.column():
|
|
77
|
+
for warning in state.warnings:
|
|
78
|
+
ui.label(f"⚠ {warning}").classes("text-xs hw-text-warning ml-2")
|
|
79
|
+
|
|
80
|
+
# Adapter Chain Section (if available, expandable, default closed)
|
|
81
|
+
if edge.chain_adapter_keys:
|
|
82
|
+
with hui.expansion_section("Adapter Chain", default_open=False):
|
|
83
|
+
with (
|
|
84
|
+
ui.card()
|
|
85
|
+
.classes("w-full p-3")
|
|
86
|
+
.style("background: var(--hw-bg-surface); border: 1px solid var(--hw-border);")
|
|
87
|
+
):
|
|
88
|
+
# Display each adapter in the chain
|
|
89
|
+
for i, adapter_key in enumerate(edge.chain_adapter_keys, 1):
|
|
90
|
+
ui.label(f"{i}. {adapter_key}").classes("text-xs hw-text-accent ml-2")
|
|
91
|
+
|
|
92
|
+
if state.execution_count > 0:
|
|
93
|
+
# Execution Statistics Section (expandable, default closed)
|
|
94
|
+
with hui.expansion_section("Execution Statistics", default_open=False):
|
|
95
|
+
exec_count = state.execution_count
|
|
96
|
+
ui.label(f"Execution Count: {exec_count}").classes("text-xs hw-text-muted ml-2")
|
|
97
|
+
|
|
98
|
+
avg_time = state.average_execution_time_us
|
|
99
|
+
if avg_time > 0:
|
|
100
|
+
ui.label(f"Average Time: {avg_time:.1f} μs").classes(
|
|
101
|
+
"text-xs hw-text-muted ml-2"
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
ui.label("Average Time: Not measured").classes("text-xs hw-text-dim ml-2")
|
|
105
|
+
|
|
106
|
+
ui.label(f"Tested value: {state.example_test_value}").classes(
|
|
107
|
+
"text-xs hw-text-muted ml-2"
|
|
108
|
+
)
|
|
109
|
+
ui.label(f"Tested result: {state.example_test_result}").classes(
|
|
110
|
+
"text-xs hw-text-muted ml-2"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Connection Path Section (Expandable, default closed)
|
|
114
|
+
with hui.expansion_section("Connection Path", default_open=False):
|
|
115
|
+
ui.label("Connection Path").classes("font-semibold text-sm")
|
|
116
|
+
ui.label(f"{edge.source_node_id} [{edge.outlet_port_id}]").classes("text-xs opacity-70")
|
|
117
|
+
ui.label("↓").classes("text-xs opacity-50 ml-2")
|
|
118
|
+
ui.label(f"{edge.sink_node_id} [{edge.inlet_port_id}]").classes("text-xs opacity-70")
|
|
119
|
+
|
|
120
|
+
# Close button
|
|
121
|
+
ui.separator().classes("my-2")
|
|
122
|
+
btn_close = ui.button("Close", on_click=lambda: self.close())
|
|
123
|
+
btn_close.props("flat")
|
|
124
|
+
btn_close.classes("w-full text-sm py-2")
|
|
125
|
+
|
|
126
|
+
popup.open()
|
|
127
|
+
self._info_popup = popup
|
|
128
|
+
|
|
129
|
+
def close(self):
|
|
130
|
+
"""Close the connection info popup."""
|
|
131
|
+
if self._info_popup:
|
|
132
|
+
self._info_popup.close()
|
|
133
|
+
self._info_popup.delete()
|
|
134
|
+
self._info_popup = None
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Event handler registration system for the enhanced graph canvas event system.
|
|
3
|
+
|
|
4
|
+
This module provides:
|
|
5
|
+
- Decorator for registering event handlers
|
|
6
|
+
- Type-safe handler registration
|
|
7
|
+
- Automatic handler discovery
|
|
8
|
+
- build_event_handler_map: scan multiple handler sources into a dispatch map
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Callable, Dict, List, Type
|
|
12
|
+
from haywire.ui.components.graph.event_definitions import BaseGraphEvent
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handles_event(*event_classes: Type[BaseGraphEvent]):
|
|
16
|
+
"""Decorator to register methods as handlers for specific event classes"""
|
|
17
|
+
|
|
18
|
+
def decorator(func: Callable):
|
|
19
|
+
setattr(func, "_handles_event_classes", event_classes)
|
|
20
|
+
return func
|
|
21
|
+
|
|
22
|
+
return decorator
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_event_handler_map(sources: List[object]) -> Dict[str, Callable]:
|
|
26
|
+
"""
|
|
27
|
+
Build an event-type → handler mapping by scanning a list of handler sources.
|
|
28
|
+
|
|
29
|
+
For each source object, scans all methods for the ``_handles_event_classes``
|
|
30
|
+
attribute set by the ``@handles_event`` decorator. When two sources register
|
|
31
|
+
the same event type the later source wins.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
sources: Objects whose methods should be scanned for ``@handles_event``.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Dict mapping event_type strings to bound handler methods.
|
|
38
|
+
"""
|
|
39
|
+
handlers: Dict[str, Callable] = {}
|
|
40
|
+
for source in sources:
|
|
41
|
+
for method_name in dir(source):
|
|
42
|
+
method = getattr(source, method_name)
|
|
43
|
+
if hasattr(method, "_handles_event_classes"):
|
|
44
|
+
for event_class in method._handles_event_classes:
|
|
45
|
+
handlers[event_class.event_type] = method
|
|
46
|
+
return handlers
|