import os import sys import pkgutil import importlib import inspect from PyQt5.QtWidgets import QApplication, QMainWindow, QAction, QMenu, QUndoStack from PyQt5.QtCore import Qt, QTimer, QRect from PyQt5.QtGui import QColor, QPainter, QPen, QBrush from OdenGraphQt import NodeGraph, BaseNode # Force qtpy to use PyQt5 explicitly os.environ["QT_API"] = "pyqt5" # --- PATCH OdenGraphQt to use QtWidgets.QUndoStack instead of QtGui.QUndoStack --- import qtpy.QtGui import qtpy.QtWidgets if not hasattr(qtpy.QtGui, "QUndoStack"): # Ensure patching only if necessary qtpy.QtGui.QUndoStack = qtpy.QtWidgets.QUndoStack # --- END PATCH --- class TransparentGraphWindow(QMainWindow): def __init__(self): super().__init__() # Enable transparency & always-on-top behavior self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool) self.setAttribute(Qt.WA_TranslucentBackground, True) # Get full-screen size for overlay screen_geometry = QApplication.primaryScreen().geometry() self.setGeometry(screen_geometry) # Create Node Graph self.graph = NodeGraph() self.graph.widget.setParent(self) self.graph.widget.setGeometry(self.rect()) # Make bottom-left corner interactive for context menu self.context_menu_area = QRect(10, self.height() - 40, 50, 30) # Load nodes dynamically self.import_nodes() # Global update timer for processing nodes self.timer = QTimer() self.timer.timeout.connect(self.update_nodes) self.timer.start(500) def import_nodes(self): """Dynamically import all custom node classes from the 'Nodes' package.""" try: package_name = 'Nodes' package = importlib.import_module(package_name) for loader, module_name, is_pkg in pkgutil.walk_packages(package.__path__, package.__name__ + "."): module = importlib.import_module(module_name) for name, obj in inspect.getmembers(module, inspect.isclass): if issubclass(obj, BaseNode) and obj.__module__ == module.__name__: self.graph.register_node(obj) except Exception as e: print(f"Error loading nodes: {e}") def update_nodes(self): """Calls process_input() on all nodes, if applicable.""" for node in self.graph.all_nodes(): if hasattr(node, "process_input"): try: node.process_input() except Exception as e: print(f"Error processing node {node}: {e}") def mousePressEvent(self, event): """Override mouse press to handle context menu in bottom-left area.""" if event.button() == Qt.RightButton and self.context_menu_area.contains(event.pos()): self.show_context_menu(event.globalPos()) def show_context_menu(self, position): """Displays a right-click context menu.""" menu = QMenu(self) quit_action = QAction("Quit", self) quit_action.triggered.connect(self.close) menu.addAction(quit_action) menu.exec_(position) def paintEvent(self, event): """Render transparent overlay and the small clickable menu area.""" painter = QPainter(self) painter.setRenderHint(QPainter.Antialiasing) # Draw semi-transparent context menu area painter.setBrush(QBrush(QColor(50, 50, 50, 150))) # Dark semi-transparent box painter.setPen(QPen(QColor(200, 200, 200, 200))) painter.drawRect(self.context_menu_area) painter.setFont(self.font()) painter.setPen(QColor(255, 255, 255)) painter.drawText(self.context_menu_area, Qt.AlignCenter, "Menu") if __name__ == "__main__": app = QApplication(sys.argv) window = TransparentGraphWindow() window.show() sys.exit(app.exec_())