105 lines
3.8 KiB
Python
105 lines
3.8 KiB
Python
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_())
|