diff --git a/Nodes/__pycache__/backdrop_node.cpython-312.pyc b/Nodes/__pycache__/backdrop_node.cpython-312.pyc index 9f205ca..dd8a2fe 100644 Binary files a/Nodes/__pycache__/backdrop_node.cpython-312.pyc and b/Nodes/__pycache__/backdrop_node.cpython-312.pyc differ diff --git a/Nodes/__pycache__/comparison_node.cpython-312.pyc b/Nodes/__pycache__/comparison_node.cpython-312.pyc new file mode 100644 index 0000000..d49ae69 Binary files /dev/null and b/Nodes/__pycache__/comparison_node.cpython-312.pyc differ diff --git a/Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc b/Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc index 0a1961f..6b78cfb 100644 Binary files a/Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc and b/Nodes/__pycache__/flyff_character_status_node.cpython-312.pyc differ diff --git a/Nodes/__pycache__/flyff_low_health_alert_node.cpython-312.pyc b/Nodes/__pycache__/flyff_low_health_alert_node.cpython-312.pyc new file mode 100644 index 0000000..35e9fd0 Binary files /dev/null and b/Nodes/__pycache__/flyff_low_health_alert_node.cpython-312.pyc differ diff --git a/Nodes/backdrop_node.py b/Nodes/backdrop_node.py index 5eaca22..2b486ca 100644 --- a/Nodes/backdrop_node.py +++ b/Nodes/backdrop_node.py @@ -1,12 +1,4 @@ #!/usr/bin/env python3 -""" -Enhanced Backdrop Node (Inherited from BaseNode) - -Features: - - Inherits from `BaseNode` so it can be discovered in your node scanning. - - Custom context menu to rename (set title) or pick a new color. - - Forces geometry updates to reduce "ghosting" or partial redraws. -""" from Qt import QtWidgets, QtGui from OdenGraphQt import BaseNode diff --git a/Nodes/comparison_node.py b/Nodes/comparison_node.py new file mode 100644 index 0000000..deb52ab --- /dev/null +++ b/Nodes/comparison_node.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +""" +Comparison Node: + - Inputs: Two input ports ("A" and "B"). + - Output: One output port ("Result"). + - Operation: A dropdown (combo menu) to select: + Equal (==), Not Equal (!=), Greater Than (>), Less Than (<), + Greater Than or Equal (>=), Less Than or Equal (<=). + - Displays the computed result in a read-only text box labeled "Result". +""" + +from OdenGraphQt import BaseNode + +class ComparisonNode(BaseNode): + __identifier__ = 'bunny-lab.io.comparison_node' + NODE_NAME = 'Comparison Node' + + def __init__(self): + super(ComparisonNode, self).__init__() + + # ---------------------------------------------------------------------- + # Initialization Section: + # - Create two input ports: A, B + # - Create one output port: Result + # - Add a combo box for logical operator selection + # - Add a text input for displaying the computed result + # ---------------------------------------------------------------------- + self.add_input('A') + self.add_input('B') + self.add_output('Result') + + # Operator combo box (==, !=, >, <, >=, <=) + self.add_combo_menu('operator', 'Operator', items=[ + 'Equal (==)', + 'Not Equal (!=)', + 'Greater Than (>)', + 'Less Than (<)', + 'Greater Than or Equal (>=)', + 'Less Than or Equal (<=)' + ]) + + # Text input for displaying the computed result. + # We'll make it read-only by accessing the underlying QLineEdit. + self.add_text_input('calc_result', 'Result', text='0') + result_widget = self.get_widget('calc_result') # This is a NodeLineEdit wrapper + if result_widget: + # Get the underlying QLineEdit + line_edit = result_widget.get_custom_widget() + # Make the QLineEdit read-only + line_edit.setReadOnly(True) + + self.value = 0 + self.set_name("Comparison Node") + self.process_input() + + def process_input(self, event=None): + """ + Compute Section: + - For each input port (A, B), if connected, grab the 'value' from + the upstream node; otherwise default to 0.0. + - Convert to float when possible, apply the selected comparison operator, + update the "Result" text box, node title, and output port. + """ + # Gather input A + input_a = self.input(0) + if input_a and input_a.connected_ports(): + a_raw = input_a.connected_ports()[0].node().get_property('value') + else: + a_raw = 0.0 + + # Gather input B + input_b = self.input(1) + if input_b and input_b.connected_ports(): + b_raw = input_b.connected_ports()[0].node().get_property('value') + else: + b_raw = 0.0 + + # Convert raw inputs to float if possible, otherwise keep as-is for string comparison. + try: + a_val = float(a_raw) + b_val = float(b_raw) + except (ValueError, TypeError): + a_val = a_raw + b_val = b_raw + + # Retrieve the selected operator from the combo box. + operator = self.get_property('operator') + result = False + + if operator == 'Equal (==)': + result = a_val == b_val + elif operator == 'Not Equal (!=)': + result = a_val != b_val + elif operator == 'Greater Than (>)': + result = a_val > b_val + elif operator == 'Less Than (<)': + result = a_val < b_val + elif operator == 'Greater Than or Equal (>=)': + result = a_val >= b_val + elif operator == 'Less Than or Equal (<=)': + result = a_val <= b_val + + # Convert boolean result to integer (1 for True, 0 for False) + self.value = 1 if result else 0 + + # Update the read-only text input and node title. + self.set_property('calc_result', str(self.value)) + + # Transmit the numeric result to any connected output nodes. + output_port = self.output(0) + if output_port and output_port.connected_ports(): + for cp in output_port.connected_ports(): + connected_node = cp.node() + if hasattr(connected_node, 'receive_data'): + connected_node.receive_data(self.value, source_port_name='Result') + + def on_input_connected(self, input_port, output_port): + self.process_input() + + def on_input_disconnected(self, input_port, output_port): + self.process_input() + + def property_changed(self, property_name): + if property_name in ['operator']: + self.process_input() + + def receive_data(self, data, source_port_name=None): + self.process_input() diff --git a/Nodes/flyff_character_status_node.py b/Nodes/flyff_character_status_node.py index b9d57f4..9275104 100644 --- a/Nodes/flyff_character_status_node.py +++ b/Nodes/flyff_character_status_node.py @@ -36,7 +36,7 @@ def get_draw_stat_port(color, border_color=None, alpha=127): class CharacterStatusNode(BaseNode): __identifier__ = 'bunny-lab.io.flyff_character_status_node' - NODE_NAME = 'Character Status' + NODE_NAME = 'Flyff - Character Status' def __init__(self): super(CharacterStatusNode, self).__init__() @@ -58,7 +58,7 @@ class CharacterStatusNode(BaseNode): self.add_output("FP: Total", painter_func=get_draw_stat_port((36, 197, 28))) self.add_output("EXP", painter_func=get_draw_stat_port((52, 195, 250))) - self.set_name("Character Status (API Disconnected)") + self.set_name("Flyff - Character Status (API Disconnected)") # Start polling timer self.timer = QtCore.QTimer() @@ -95,7 +95,7 @@ class CharacterStatusNode(BaseNode): else: print(f"[WARNING] Unexpected API key: {key} (not mapped)") - self.set_name("Character Status (API Connected)") + self.set_name("Flyff - Character Status (API Connected)") self.update() self.transmit_data() @@ -104,14 +104,14 @@ class CharacterStatusNode(BaseNode): print("[ERROR] Stack Trace:\n", traceback.format_exc()) else: print("[ERROR] Unexpected API response format (not a dict):", data) - self.set_name("Character Status (API Disconnected)") + self.set_name("Flyff - Character Status (API Disconnected)") else: print(f"[ERROR] API request failed with status code {response.status_code}") - self.set_name("Character Status (API Disconnected)") + self.set_name("Flyff - Character Status (API Disconnected)") except Exception as e: - self.set_name("Character Status (API Disconnected)") + self.set_name("Flyff - Character Status (API Disconnected)") print("[ERROR] Error polling API in CharacterStatusNode:", str(e)) print("[ERROR] Stack Trace:\n", traceback.format_exc()) diff --git a/Nodes/flyff_low_health_alert_node.py b/Nodes/flyff_low_health_alert_node.py new file mode 100644 index 0000000..708c69e --- /dev/null +++ b/Nodes/flyff_low_health_alert_node.py @@ -0,0 +1,22 @@ +from OdenGraphQt import BaseNode +from OdenGraphQt.constants import NodePropWidgetEnum +from OdenGraphQt.widgets.node_widgets import NodeLineEditValidatorCheckBox + +class CheckboxNode(BaseNode): + + # 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__() + + # 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)) \ No newline at end of file diff --git a/borealis_transparent.py b/borealis_transparent.py new file mode 100644 index 0000000..053a6fa --- /dev/null +++ b/borealis_transparent.py @@ -0,0 +1,104 @@ +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_()) diff --git a/debug_processed.png b/debug_processed.png index 4d17f78..85eebd7 100644 Binary files a/debug_processed.png and b/debug_processed.png differ diff --git a/debug_screenshot.png b/debug_screenshot.png index 199633c..8d951d6 100644 Binary files a/debug_screenshot.png and b/debug_screenshot.png differ diff --git a/screen_overlays.py b/screen_overlays.py new file mode 100644 index 0000000..4ce2665 --- /dev/null +++ b/screen_overlays.py @@ -0,0 +1,80 @@ +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_())