Implemented ReactJS Application Server with basic functionality.

This commit is contained in:
2025-03-20 02:43:13 -06:00
parent 5e6ad27f71
commit cffad033fa
11 changed files with 240 additions and 0 deletions

View File

@ -0,0 +1,78 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Shapes 1.15
import QtQuick.Window 2.15
Item {
id: root
width: Screen.width
height: Screen.height
// Grid overlay is enabled at startup.
property bool editMode: true
// Blue gradient background (edges fading inward) with stops shifted inward.
Rectangle {
id: gradientBackground
width: parent.width
height: parent.height
opacity: 0.5
gradient: Gradient {
// Shifted stops: outer stops moved to 0.1 and 0.9, inner stops to 0.4 and 0.6.
GradientStop { position: 0.1; color: Qt.rgba(0, 100/255, 255/255, 0.5) }
GradientStop { position: 0.4; color: Qt.rgba(0, 50/255, 180/255, 0.2) }
GradientStop { position: 0.5; color: Qt.rgba(0, 0, 0, 0.0) }
GradientStop { position: 0.6; color: Qt.rgba(0, 50/255, 180/255, 0.2) }
GradientStop { position: 0.9; color: Qt.rgba(0, 100/255, 255/255, 0.5) }
}
visible: editMode // Only show the gradient in edit mode
}
// Top & Bottom fade remains unchanged.
Rectangle {
id: topBottomGradient
width: parent.width
height: parent.height
opacity: 0.3
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop { position: 0.0; color: Qt.rgba(0, 100/255, 255/255, 0.4) }
GradientStop { position: 0.3; color: Qt.rgba(0, 50/255, 180/255, 0.1) }
GradientStop { position: 0.5; color: Qt.rgba(0, 0, 0, 0.0) }
GradientStop { position: 0.7; color: Qt.rgba(0, 50/255, 180/255, 0.1) }
GradientStop { position: 1.0; color: Qt.rgba(0, 100/255, 255/255, 0.4) }
}
visible: editMode
}
// Full-Screen Dynamic Grid with 10% increased transparency (grid lines at 0.3 opacity).
Canvas {
id: gridCanvas
width: parent.width
height: parent.height
onPaint: {
var ctx = getContext("2d");
ctx.clearRect(0, 0, width, height);
ctx.strokeStyle = "rgba(255, 255, 255, 0.3)"; // Reduced opacity from 0.4 to 0.3.
ctx.lineWidth = 1;
var step = 120; // Grid spacing remains unchanged.
for (var x = 0; x < width; x += step) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, height);
ctx.stroke();
}
for (var y = 0; y < height; y += step) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(width, y);
ctx.stroke();
}
}
Component.onCompleted: requestPaint()
onVisibleChanged: requestPaint()
visible: editMode // Hide when edit mode is off.
}
}

View File

@ -0,0 +1,193 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import pkgutil
import importlib
import inspect
import types
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QWidget
from PyQt5.QtCore import Qt, QUrl, QTimer
from PyQt5.QtGui import QGuiApplication
from PyQt5.QtQuick import QQuickView
# OdenGraphQt Fix: Monkey-patch QUndoStack
import OdenGraphQt.base.graph as base_graph
from PyQt5 import QtWidgets
base_graph.QtGui.QUndoStack = QtWidgets.QUndoStack
import OdenGraphQt.base.commands as base_commands
_original_redo = base_commands.NodesRemovedCmd.redo
_original_undo = base_commands.NodesRemovedCmd.undo
def _patched_redo(self):
try:
_original_redo(self)
except TypeError as e:
if "unexpected type" in str(e) and hasattr(self, 'node'):
node_ids = []
if isinstance(self.node, list):
node_ids = [getattr(n, 'id', str(n)) for n in self.node]
else:
node_ids = [getattr(self.node, 'id', str(self.node))]
self.graph.nodes_deleted.emit(node_ids)
else:
raise
def _patched_undo(self):
try:
_original_undo(self)
except TypeError as e:
if "unexpected type" in str(e) and hasattr(self, 'node'):
node_ids = []
if isinstance(self.node, list):
node_ids = [getattr(n, 'id', str(n)) for n in self.node]
else:
node_ids = [getattr(self.node, 'id', str(self.node))]
self.graph.nodes_deleted.emit(node_ids)
else:
raise
base_commands.NodesRemovedCmd.redo = _patched_redo
base_commands.NodesRemovedCmd.undo = _patched_undo
# OdenGraphQt Transparent Viewer
from OdenGraphQt.widgets.viewer import NodeViewer
class TransparentViewer(NodeViewer):
"""A NodeViewer that does not paint anything in drawBackground() -> Fully transparent."""
def drawBackground(self, painter, rect):
pass # Do nothing, ensuring transparency.
# NodeGraph & Node Import Helpers
from OdenGraphQt import NodeGraph, BaseNode
def import_nodes_from_folder(package_name):
imported_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__:
imported_nodes.append(obj)
return imported_nodes
def make_node_command(graph, node_type):
def command():
try:
graph.create_node(node_type)
except Exception as e:
print(f"Error creating node of type {node_type}: {e}")
return command
# Edit Mode Button
class EditButton(QPushButton):
"""A small, frameless button to toggle edit mode."""
def __init__(self, parent=None):
super().__init__("Toggle Edit Mode", parent)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
# Dark gray background with white text.
self.setStyleSheet("background-color: #444444; border: 1px solid black; color: white;")
self.resize(140, 40)
# Main Overlay Window
class MainWindow(QMainWindow):
"""A frameless, transparent overlay with OdenGraphQt nodes & edit mode toggle."""
def __init__(self):
super().__init__()
# Full-screen overlay
app = QApplication.instance()
screen_geo = app.primaryScreen().geometry()
self.setGeometry(screen_geo)
# Frameless, top-most, fully transparent
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground, True)
# QML Background
self.qml_view = QQuickView()
self.qml_view.setSource(QUrl("qml/background_grid.qml"))
self.qml_view.setFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.qml_view.setClearBeforeRendering(True)
self.qml_view.setColor(Qt.transparent)
self.qml_view.show()
# Save the QML root object for later property sync
self.qml_root = self.qml_view.rootObject()
# NodeGraph with TransparentViewer
self.graph = NodeGraph(viewer=TransparentViewer())
self.nodeGraphWidget = self.graph.widget
self.nodeGraphWidget.setStyleSheet("background: transparent; border: none;")
# Transparent central widget
central = QWidget(self)
central.setAttribute(Qt.WA_TranslucentBackground, True)
self.setCentralWidget(central)
self.nodeGraphWidget.setParent(central)
self.nodeGraphWidget.setGeometry(central.rect())
# Edit Mode Button (Python controlled)
self.editButton = EditButton(self)
self.editButton.move(10, 10)
self.editButton.clicked.connect(self.toggleEditMode)
self.isEditMode = True # Set edit mode enabled by default
# Ensure QML grid overlay is enabled at startup
if self.qml_root:
self.qml_root.setProperty("editMode", self.isEditMode)
# Import custom nodes
try:
custom_nodes = import_nodes_from_folder('Nodes')
for node_class in custom_nodes:
self.graph.register_node(node_class)
graph_menu = self.graph.get_context_menu('graph')
for node_class in custom_nodes:
node_type = f"{node_class.__identifier__}.{node_class.__name__}"
node_name = node_class.NODE_NAME
graph_menu.add_command(
f"Add {node_name}",
make_node_command(self.graph, node_type)
)
except Exception as e:
print(f"Error setting up custom nodes: {e}")
# Global update timer
self.timer = QTimer(self)
self.timer.timeout.connect(self.global_update)
self.timer.start(500)
# Timer to ensure the button stays on top (hacky, but effective)
self.raiseTimer = QTimer(self)
self.raiseTimer.timeout.connect(self.editButton.raise_)
self.raiseTimer.start(1000) # Raise the button every 1 second
self.show()
self.nodeGraphWidget.setAttribute(Qt.WA_TransparentForMouseEvents, not self.isEditMode)
def toggleEditMode(self):
"""Toggle edit mode (pass-through clicks vs interactive)."""
self.isEditMode = not self.isEditMode
self.nodeGraphWidget.setAttribute(Qt.WA_TransparentForMouseEvents, not self.isEditMode)
# Button text remains constant.
self.editButton.setText("Toggle Edit Mode")
if self.qml_root:
self.qml_root.setProperty("editMode", self.isEditMode)
def global_update(self):
"""Update all nodes periodically."""
for node in self.graph.all_nodes():
if hasattr(node, "process_input"):
node.process_input()
# Entry Point
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

View File

@ -0,0 +1,160 @@
import sys
import pkgutil
import importlib
import inspect
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QGraphicsView, QGraphicsScene, QGraphicsItem, QMenu
from PyQt5.QtCore import Qt, QTimer, QRectF, QPointF
from PyQt5.QtGui import QColor, QPainter, QPen, QBrush, QGradient, QLinearGradient
from PyQt5 import QtWidgets, QtCore, QtGui
from OdenGraphQt import NodeGraph, BaseNode
# --- Fix Missing QUndoStack in QtGui ---
import OdenGraphQt.base.graph as base_graph
base_graph.QtGui.QUndoStack = QtWidgets.QUndoStack # Monkey-patch the missing QUndoStack
# --- Custom Graph Scene ---
class CustomGraphScene(QGraphicsScene):
"""
Custom scene that draws a blueprint-style transparent grid with gradient shading.
"""
def __init__(self, parent=None):
super().__init__(parent)
self.setBackgroundBrush(QtCore.Qt.transparent)
self.grid_color = QtGui.QColor(100, 160, 160, 160) # Blueprint grid color (10% more transparent)
self.grid_size = 115
def drawBackground(self, painter, rect):
"""
Custom draw function to render a blueprint-style grid with gradient shading.
"""
painter.save()
painter.setRenderHint(QPainter.Antialiasing, False)
painter.setBrush(QtCore.Qt.NoBrush) # No background fill
pen = QPen(self.grid_color, 0.5)
left = int(rect.left()) - (int(rect.left()) % self.grid_size)
top = int(rect.top()) - (int(rect.top()) % self.grid_size)
# Draw vertical lines
lines = []
for x in range(left, int(rect.right()), self.grid_size):
lines.append(QtCore.QLineF(x, rect.top(), x, rect.bottom()))
# Draw horizontal lines
for y in range(top, int(rect.bottom()), self.grid_size):
lines.append(QtCore.QLineF(rect.left(), y, rect.right(), y))
painter.setPen(pen)
painter.drawLines(lines)
# Draw gradient shading (top and bottom)
gradient = QLinearGradient(QPointF(rect.left(), rect.top()), QPointF(rect.left(), rect.bottom()))
gradient.setColorAt(0.0, QColor(0, 40, 100, 220)) # Darker blue at the top
gradient.setColorAt(0.5, QColor(0, 0, 0, 0)) # Transparent in the middle
gradient.setColorAt(1.0, QColor(0, 40, 100, 220)) # Darker blue at the bottom
painter.fillRect(rect, QBrush(gradient))
painter.restore()
# --- Node Management ---
def import_nodes_from_folder(package_name):
imported_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__:
imported_nodes.append(obj)
return imported_nodes
# --- Custom Graph View ---
class CustomGraphView(QGraphicsView):
"""
Custom view for the graph that applies full transparency and handles right-click context menu.
"""
def __init__(self, scene, graph, parent=None):
super().__init__(scene, parent)
self.graph = graph # Reference to NodeGraph
self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setStyleSheet("background: transparent; border: none;")
self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
# Enable context menu on right-click
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.show_context_menu)
def show_context_menu(self, position):
"""
Displays the node creation context menu with dynamically loaded nodes.
"""
menu = QMenu()
for node_class in self.graph.registered_nodes():
node_name = getattr(node_class, "NODE_NAME", node_class.__name__)
menu.addAction(f"Create {node_name}", lambda nc=node_class: self.create_node(nc))
menu.exec_(self.mapToGlobal(position))
def create_node(self, node_class):
"""
Creates a node instance of the given class in the NodeGraph.
"""
try:
node = self.graph.create_node(f"{node_class.__identifier__}.{node_class.__name__}")
print(f"Created node: {node_class.__name__}")
except Exception as e:
print(f"Error creating node: {e}")
# --- Main Window ---
class MainWindow(QMainWindow):
"""A frameless, transparent overlay with a custom graph."""
def __init__(self):
super().__init__()
# Full-screen overlay
app = QApplication.instance()
screen_geo = app.primaryScreen().geometry()
self.setGeometry(screen_geo)
# Frameless, top-most, fully transparent
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.setAttribute(Qt.WA_TranslucentBackground, True)
# Transparent central widget
central = QWidget(self)
central.setAttribute(Qt.WA_TranslucentBackground, True)
layout = QVBoxLayout(central)
layout.setContentsMargins(0, 0, 0, 0)
self.setCentralWidget(central)
# Initialize NodeGraph
self.graph = NodeGraph()
# Load custom nodes
custom_nodes = import_nodes_from_folder('Nodes')
for node_class in custom_nodes:
self.graph.register_node(node_class)
# Initialize Custom Graph Scene & View
self.scene = CustomGraphScene()
self.view = CustomGraphView(self.scene, self.graph, self)
layout.addWidget(self.view)
# Global update timer
self.timer = QTimer(self)
self.timer.timeout.connect(self.global_update)
self.timer.start(500)
def global_update(self):
"""Update all nodes periodically."""
for node in self.graph.all_nodes():
if hasattr(node, "process_input"):
node.process_input()
# --- Entry Point ---
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())