Added Functional Health Warning
This commit is contained in:
parent
9db8cc6b19
commit
d9623193d8
@ -2,10 +2,15 @@
|
||||
import pkgutil
|
||||
import importlib
|
||||
import inspect
|
||||
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QGraphicsView, QGraphicsScene, QGraphicsItem
|
||||
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):
|
||||
@ -51,13 +56,25 @@ class CustomGraphScene(QGraphicsScene):
|
||||
|
||||
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.
|
||||
Custom view for the graph that applies full transparency and handles right-click context menu.
|
||||
"""
|
||||
def __init__(self, scene, parent=None):
|
||||
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)
|
||||
@ -65,6 +82,30 @@ class CustomGraphView(QGraphicsView):
|
||||
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."""
|
||||
@ -87,9 +128,17 @@ class MainWindow(QMainWindow):
|
||||
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.view = CustomGraphView(self.scene, self.graph, self)
|
||||
layout.addWidget(self.view)
|
||||
|
||||
# Global update timer
|
||||
@ -98,8 +147,10 @@ class MainWindow(QMainWindow):
|
||||
self.timer.start(500)
|
||||
|
||||
def global_update(self):
|
||||
"""Update all nodes periodically (to be implemented)."""
|
||||
pass
|
||||
"""Update all nodes periodically."""
|
||||
for node in self.graph.all_nodes():
|
||||
if hasattr(node, "process_input"):
|
||||
node.process_input()
|
||||
|
||||
# --- Entry Point ---
|
||||
if __name__ == '__main__':
|
@ -1,80 +0,0 @@
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication, QWidget
|
||||
from PyQt5.QtCore import Qt, QRect, QPoint
|
||||
from PyQt5.QtGui import QPainter, QPen, QColor, QFont, QFontMetrics
|
||||
|
||||
class OverlayCanvas(QWidget):
|
||||
"""
|
||||
UI overlay for drawing and interacting with on-screen elements.
|
||||
"""
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# **Full-screen overlay**
|
||||
screen_geo = QApplication.primaryScreen().geometry()
|
||||
self.setGeometry(screen_geo) # Set to full screen
|
||||
|
||||
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
|
||||
self.setAttribute(Qt.WA_TranslucentBackground, True)
|
||||
self.setAttribute(Qt.WA_NoSystemBackground, True)
|
||||
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
|
||||
self.setAttribute(Qt.WA_AlwaysStackOnTop, True)
|
||||
|
||||
# **Helper Object: Low Health Alert**
|
||||
self.helper_LowHealthAlert = QRect(250, 300, 900, 35) # Adjusted width and height
|
||||
self.dragging = False
|
||||
self.resizing = False
|
||||
self.drag_offset = QPoint()
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""Draw the helper overlay objects."""
|
||||
painter = QPainter(self)
|
||||
painter.setPen(Qt.NoPen)
|
||||
painter.setBrush(QColor(255, 0, 0)) # Solid red rectangle
|
||||
painter.drawRect(self.helper_LowHealthAlert)
|
||||
|
||||
# Draw bold white text centered within the rectangle
|
||||
font = QFont("Arial", 14, QFont.Bold) # Scaled text
|
||||
painter.setFont(font)
|
||||
painter.setPen(QColor(255, 255, 255))
|
||||
|
||||
text = "LOW HEALTH"
|
||||
metrics = QFontMetrics(font)
|
||||
text_width = metrics.horizontalAdvance(text)
|
||||
text_height = metrics.height()
|
||||
text_x = self.helper_LowHealthAlert.center().x() - text_width // 2
|
||||
text_y = self.helper_LowHealthAlert.center().y() + text_height // 4
|
||||
|
||||
painter.drawText(text_x, text_y, text)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""Detect clicks for dragging and resizing the helper object."""
|
||||
if event.button() == Qt.LeftButton:
|
||||
if self.helper_LowHealthAlert.contains(event.pos()):
|
||||
if event.pos().x() > self.helper_LowHealthAlert.right() - 10 and event.pos().y() > self.helper_LowHealthAlert.bottom() - 10:
|
||||
self.resizing = True
|
||||
else:
|
||||
self.dragging = True
|
||||
self.drag_offset = event.pos() - self.helper_LowHealthAlert.topLeft()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
"""Handle dragging and resizing movements."""
|
||||
if self.dragging:
|
||||
self.helper_LowHealthAlert.moveTopLeft(event.pos() - self.drag_offset)
|
||||
self.update()
|
||||
elif self.resizing:
|
||||
new_width = max(150, event.pos().x() - self.helper_LowHealthAlert.x())
|
||||
new_height = max(20, event.pos().y() - self.helper_LowHealthAlert.y())
|
||||
self.helper_LowHealthAlert.setSize(new_width, new_height)
|
||||
self.update()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
"""End dragging or resizing event."""
|
||||
self.dragging = False
|
||||
self.resizing = False
|
||||
|
||||
if __name__ == '__main__':
|
||||
app_gui = QApplication(sys.argv)
|
||||
overlay_window = OverlayCanvas()
|
||||
overlay_window.show()
|
||||
sys.exit(app_gui.exec_())
|
Binary file not shown.
@ -1,22 +1,177 @@
|
||||
import time
|
||||
import sys
|
||||
from OdenGraphQt import BaseNode
|
||||
from OdenGraphQt.constants import NodePropWidgetEnum
|
||||
from OdenGraphQt.widgets.node_widgets import NodeLineEditValidatorCheckBox
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
class CheckboxNode(BaseNode):
|
||||
# Attempt to import winsound (Windows-only)
|
||||
try:
|
||||
import winsound
|
||||
HAS_WINSOUND = True
|
||||
except ImportError:
|
||||
HAS_WINSOUND = False
|
||||
|
||||
|
||||
class OverlayCanvas(QtWidgets.QWidget):
|
||||
"""
|
||||
UI overlay for displaying a red warning box, which can be repositioned by dragging.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# **Full-screen overlay**
|
||||
screen_geo = QtWidgets.QApplication.primaryScreen().geometry()
|
||||
self.setGeometry(screen_geo) # Set to full screen
|
||||
|
||||
self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)
|
||||
self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
|
||||
self.setAttribute(QtCore.Qt.WA_NoSystemBackground, True)
|
||||
self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, False)
|
||||
self.setAttribute(QtCore.Qt.WA_AlwaysStackOnTop, True)
|
||||
|
||||
# **Draggable Low Health Alert**
|
||||
self.helper_LowHealthAlert = QtCore.QRect(250, 300, 900, 35) # Default Position
|
||||
self.dragging = False
|
||||
self.drag_offset = QtCore.QPoint()
|
||||
|
||||
self.setVisible(False) # Initially hidden
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""Draw the helper overlay objects."""
|
||||
if not self.isVisible():
|
||||
return # Don't draw anything if invisible
|
||||
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
painter.setBrush(QtGui.QColor(255, 0, 0)) # Solid red rectangle
|
||||
painter.drawRect(self.helper_LowHealthAlert)
|
||||
|
||||
# Draw bold white text centered within the rectangle
|
||||
font = QtGui.QFont("Arial", 14, QtGui.QFont.Bold)
|
||||
painter.setFont(font)
|
||||
painter.setPen(QtGui.QColor(255, 255, 255))
|
||||
|
||||
text = "LOW HEALTH"
|
||||
metrics = QtGui.QFontMetrics(font)
|
||||
text_width = metrics.horizontalAdvance(text)
|
||||
text_height = metrics.height()
|
||||
text_x = self.helper_LowHealthAlert.center().x() - text_width // 2
|
||||
text_y = self.helper_LowHealthAlert.center().y() + text_height // 4
|
||||
|
||||
painter.drawText(text_x, text_y, text)
|
||||
|
||||
def toggle_alert(self, state):
|
||||
"""
|
||||
Show or hide the overlay based on the state (1 = show, 0 = hide).
|
||||
"""
|
||||
self.setVisible(state == 1)
|
||||
self.update()
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""Detect clicks inside the red box and allow dragging."""
|
||||
if event.button() == QtCore.Qt.LeftButton and self.helper_LowHealthAlert.contains(event.pos()):
|
||||
self.dragging = True
|
||||
self.drag_offset = event.pos() - self.helper_LowHealthAlert.topLeft()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
"""Handle dragging movement."""
|
||||
if self.dragging:
|
||||
new_x = event.pos().x() - self.drag_offset.x()
|
||||
new_y = event.pos().y() - self.drag_offset.y()
|
||||
self.helper_LowHealthAlert.moveTopLeft(QtCore.QPoint(new_x, new_y))
|
||||
self.update()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
"""Stop dragging when the mouse button is released."""
|
||||
self.dragging = False
|
||||
|
||||
|
||||
class FlyffLowHealthAlertNode(BaseNode):
|
||||
"""
|
||||
Custom OdenGraphQt node that toggles a visual alert overlay and plays a beep when health is low.
|
||||
"""
|
||||
|
||||
# set a unique node identifier.
|
||||
__identifier__ = 'bunny-lab.io.flyff_low_health_alert_node'
|
||||
|
||||
# set the initial default node name.
|
||||
NODE_NAME = 'Flyff - Low Health Alert'
|
||||
|
||||
def __init__(self):
|
||||
super(CheckboxNode, self).__init__()
|
||||
overlay_instance = None # Shared overlay instance
|
||||
last_beep_time = 0 # Time tracking for beep interval
|
||||
BEEP_INTERVAL_SECONDS = 2 # Beep every 2 seconds
|
||||
|
||||
# Create checkboxes to decide which kind of alert(s) to utilize.
|
||||
def __init__(self):
|
||||
super(FlyffLowHealthAlertNode, self).__init__()
|
||||
|
||||
# Create checkboxes to decide which kind of alert(s) to utilize
|
||||
self.add_checkbox('cb_1', '', 'Sound Alert', True)
|
||||
self.add_checkbox('cb_2', '', 'Visual Alert', True)
|
||||
|
||||
# Create Input Port
|
||||
self.add_input('Toggle (1 = On | 0 = Off)', color=(200, 100, 0))
|
||||
#self.add_output('out', color=(0, 100, 200))
|
||||
|
||||
# Add text input widget to display received value
|
||||
self.add_text_input('value', 'Current Value', text='0')
|
||||
|
||||
# Ensure only one overlay instance exists
|
||||
if not FlyffLowHealthAlertNode.overlay_instance:
|
||||
FlyffLowHealthAlertNode.overlay_instance = OverlayCanvas()
|
||||
FlyffLowHealthAlertNode.overlay_instance.show()
|
||||
|
||||
def process_input(self):
|
||||
"""
|
||||
This function runs every 500ms (via the global update loop).
|
||||
It updates the displayed value and toggles the alert if needed.
|
||||
"""
|
||||
input_port = self.input(0)
|
||||
|
||||
# If there is a connected node, fetch its output value
|
||||
if input_port.connected_ports():
|
||||
connected_node = input_port.connected_ports()[0].node()
|
||||
if hasattr(connected_node, 'get_property'):
|
||||
value = connected_node.get_property('value')
|
||||
else:
|
||||
value = "0"
|
||||
else:
|
||||
value = "0" # Default to zero if nothing is connected
|
||||
|
||||
try:
|
||||
input_value = int(value) # Ensure we interpret input as an integer (0 or 1)
|
||||
except ValueError:
|
||||
input_value = 0 # Default to off if the input is not valid
|
||||
|
||||
# Update the value display box
|
||||
self.set_property('value', str(input_value))
|
||||
|
||||
# Check if the "Visual Alert" checkbox is enabled
|
||||
visual_alert_enabled = self.get_property('cb_2')
|
||||
|
||||
# Ensure that if "Visual Alert" is unchecked, the overlay is always hidden
|
||||
if not visual_alert_enabled:
|
||||
FlyffLowHealthAlertNode.overlay_instance.toggle_alert(0)
|
||||
else:
|
||||
FlyffLowHealthAlertNode.overlay_instance.toggle_alert(input_value)
|
||||
|
||||
# Check if "Sound Alert" is enabled and beep if necessary
|
||||
self.handle_beep(input_value)
|
||||
|
||||
def handle_beep(self, input_value):
|
||||
"""
|
||||
Plays a beep sound every 2 seconds when the value is `1` and "Sound Alert" is enabled.
|
||||
"""
|
||||
sound_alert_enabled = self.get_property('cb_1')
|
||||
current_time = time.time()
|
||||
|
||||
if input_value == 1 and sound_alert_enabled:
|
||||
if (current_time - FlyffLowHealthAlertNode.last_beep_time) >= FlyffLowHealthAlertNode.BEEP_INTERVAL_SECONDS:
|
||||
FlyffLowHealthAlertNode.last_beep_time = current_time
|
||||
self.play_beep()
|
||||
else:
|
||||
FlyffLowHealthAlertNode.last_beep_time = 0 # Reset when health is safe
|
||||
|
||||
def play_beep(self):
|
||||
"""
|
||||
Plays a beep using `winsound.Beep` (Windows) or prints a terminal bell (`\a`).
|
||||
"""
|
||||
if HAS_WINSOUND:
|
||||
winsound.Beep(376, 100) # 376 Hz, 100ms duration
|
||||
else:
|
||||
print('\a', end='') # Terminal bell for non-Windows systems
|
||||
|
Binary file not shown.
@ -88,7 +88,7 @@ if __name__ == '__main__':
|
||||
)
|
||||
|
||||
# Resize and show the graph widget.
|
||||
graph.widget.resize(1200, 800)
|
||||
graph.widget.resize(1920, 1080)
|
||||
graph.widget.show()
|
||||
|
||||
# Global update timer:
|
||||
|
Loading…
x
Reference in New Issue
Block a user