#!/usr/bin/env python3 """ Modified Flow UI Application: - The character node is now named "Character Status" (display title). - DisplayValueNodes dynamically update their title to show the value from the connected output port. - Newly spawned DisplayValueNodes (via the context menu) follow the same dynamic behavior. - Node creation strings now include the class name to match NodeGraphQt requirements. """ import sys import requests from Qt import QtWidgets, QtCore, QtGui from NodeGraphQt import NodeGraph, BaseNode # ----------------------------------------------------------------------------- # Custom Port Painter Function for Character Status Node Ports (Semi-Transparent) # ----------------------------------------------------------------------------- def get_draw_stat_port(color, border_color=None, alpha=127): """ Returns a custom port painter function that draws a circular port with a semi-transparent fill, then draws text (port label and current value) next to it. Args: color (tuple): RGB tuple for the fill color. border_color (tuple, optional): RGB tuple for the border color. Defaults to the same as color if not provided. alpha (int, optional): Alpha value (0-255) for transparency. Default is 127. """ if border_color is None: border_color = color def painter_func(painter, rect, info): painter.save() # --- Section: Port Drawing --- pen = QtGui.QPen(QtGui.QColor(*border_color)) pen.setWidth(1.8) painter.setPen(pen) # Create semi-transparent fill using the provided alpha value. semi_transparent_color = QtGui.QColor(color[0], color[1], color[2], alpha) painter.setBrush(semi_transparent_color) painter.drawEllipse(rect) # --- Section: Port Label and Value Drawing --- port = info.get('port') if port is not None: node = port.node() # Parent node (e.g. Character Status) stat = port.name() # Port label (e.g. "HP") # Retrieve the current value from the node's values dict (if available) value = node.values.get(stat, "N/A") if hasattr(node, 'values') else "N/A" # Create a text rectangle to the right of the port. text_rect = rect.adjusted(rect.width() + 4, 0, rect.width() + 70, 0) painter.setPen(QtGui.QColor(0, 0, 0)) painter.drawText(text_rect, QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft, f"{stat}: {value}") painter.restore() return painter_func # ----------------------------------------------------------------------------- # Custom Node Classes # ----------------------------------------------------------------------------- class CharacterStatusNode(BaseNode): """ Character Status Node This node represents the character's current status. It has no input ports and four output ports: "HP", "MP", "FP", and "EXP". The current values are stored in self.values and are drawn using a custom painter. """ __identifier__ = 'io.github.nicole.status' NODE_NAME = 'Character Status' def __init__(self): super(CharacterStatusNode, self).__init__() # --- Section: Initialization of Stat Values and Ports --- self.values = {"HP": "N/A", "MP": "N/A", "FP": "N/A", "EXP": "N/A"} self.add_output("HP", painter_func=get_draw_stat_port((255, 0, 0))) # Red for HP self.add_output("MP", painter_func=get_draw_stat_port((0, 0, 255))) # Blue for MP self.add_output("FP", painter_func=get_draw_stat_port((0, 255, 0))) # Green for FP self.add_output("EXP", painter_func=get_draw_stat_port((127, 255, 212))) # Aquamarine for EXP # Set the node title to "Character Status" self.set_name("Character Status") def update_value(self, stat, value): """ Update the value for a given stat. Args: stat (str): Stat name (e.g., "HP", "MP", etc.). value (str): New value for the stat. """ self.values[stat] = value class DisplayValueNode(BaseNode): """ Display Value Node This node displays a single input value. Its title is updated to show the value from the connected output port. If no port is connected, it displays "Display Value: No Input Configured". """ __identifier__ = 'io.github.nicole.display' NODE_NAME = 'Display Value Node' def __init__(self): super(DisplayValueNode, self).__init__() # --- Section: Initialization and Default State --- self.add_input("in") self.add_output("out") self.value = "No Input Configured" self.update_display() def update_display(self): """ Update the node's title to reflect the current displayed value. """ self.set_name(f"Display Value: {self.value}") def process_input(self): """ Processes the input connection: - If connected, retrieve the value from the connected node's output port. - If not, set the value to "No Input Configured". """ input_port = self.input(0) connections = input_port.connections() if input_port is not None else [] if connections: # --- Section: Processing Connected Input --- connection = connections[0] out_port = connection.out_port() if out_port: parent_node = out_port.node() port_name = out_port.name() self.value = parent_node.values.get(port_name, "N/A") else: self.value = "No Input Configured" else: # --- Section: No Connection Found --- self.value = "No Input Configured" self.update_display() # ----------------------------------------------------------------------------- # API Polling Functions # ----------------------------------------------------------------------------- def poll_api(): """ Polls the Flask API for JSON data. Expected JSON format: {"hp": "150/150", "mp": "75/100", "fp": "20/20", "exp": "10.0000"} Returns: dict: The JSON data, or None on error. """ api_url = "http://127.0.0.1:5000/data" try: response = requests.get(api_url, timeout=1) if response.status_code == 200: return response.json() else: print("API error: status code", response.status_code) except Exception as e: print("Error polling API:", e) return None def update_nodes(graph, character_status_node): """ Polls the API, updates the Character Status node's values, and then updates all DisplayValueNodes in the graph. Args: graph (NodeGraph): The NodeGraphQt graph instance. character_status_node (CharacterStatusNode): The character status node. """ data = poll_api() if data is None: return # --- Section: Update Character Status Node with Latest Data --- character_status_node.update_value("HP", data.get('hp', "N/A")) character_status_node.update_value("MP", data.get('mp', "N/A")) character_status_node.update_value("FP", data.get('fp', "N/A")) character_status_node.update_value("EXP", data.get('exp', "N/A")) # --- Section: Update All DisplayValueNodes --- for node in graph.all_nodes(): if hasattr(node, "process_input"): node.process_input() # ----------------------------------------------------------------------------- # Context Menu: Add DisplayValueNode Command # ----------------------------------------------------------------------------- def add_display_node(graph): """ Adds a new DisplayValueNode to the graph at a default position. Args: graph (NodeGraph): The NodeGraphQt graph instance. """ # Use NodeGraphQt's create_node() with the correct node type string. new_node = graph.create_node('io.github.nicole.display.DisplayValueNode', name='Display Value Node', pos=[400, 300]) print("Added a new Display Value node.") # ----------------------------------------------------------------------------- # Main Application # ----------------------------------------------------------------------------- if __name__ == '__main__': app = QtWidgets.QApplication([]) # --- Section: Setup NodeGraphQT --- graph = NodeGraph() graph.widget.setWindowTitle("Project Borealis - Flyff Information Overlay") # Register nodes using their classes. graph.register_node(CharacterStatusNode) graph.register_node(DisplayValueNode) # --- Section: Setup Context Menu for Adding DisplayValueNodes --- graph_context_menu = graph.get_context_menu('graph') graph_context_menu.add_command( 'Add Display Value Node', lambda: add_display_node(graph), shortcut='Ctrl+D' ) # --- Section: Create and Connect Nodes --- # Create the Character Status node. character_status_node = graph.create_node('io.github.nicole.status.CharacterStatusNode', name='Character Status', pos=[0, 0]) # Create a Display Value node. display_node = graph.create_node('io.github.nicole.display.DisplayValueNode', name='Display Value Node', pos=[500, 200]) # Connect the EXP output (index 3) from the Character Status node to the display node's input. character_status_node.set_output(3, display_node.input(0)) # --- Section: Timer Setup for API Polling and Node Updates --- timer = QtCore.QTimer() timer.timeout.connect(lambda: update_nodes(graph, character_status_node)) timer.start(500) graph.widget.resize(1000, 600) graph.widget.show() app.exec_()