2026-10-26 23:55:35 +01:00
|
|
|
#ifndef XTC_GRAPH_WIDGET_HPP
|
|
|
|
|
#define XTC_GRAPH_WIDGET_HPP
|
|
|
|
|
#include <FL/Fl_Widget.H>
|
|
|
|
|
#include <array>
|
|
|
|
|
#include "xtc/xtc_engine.h"
|
2026-10-27 01:37:21 +01:00
|
|
|
|
2026-10-26 23:55:35 +01:00
|
|
|
class Fl_Box;
|
|
|
|
|
|
|
|
|
|
/* GUI positions/selection only. Domain snapshots and edits stay in the C API. */
|
|
|
|
|
class GraphWidget : public Fl_Widget {
|
2026-10-27 01:37:21 +01:00
|
|
|
public:
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Applies a graph edit using an opaque context and optional added-node output.
|
|
|
|
|
*
|
|
|
|
|
* The handler receives the context, edit command, and output identifier in that order.
|
|
|
|
|
* @return XTC_OK on success, or an edit error.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
using EditHandler = xtc_result (*)(void *, const xtc_graph_edit &, size_t *);
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Constructs a graph widget bound to a graph and an edit callback.
|
|
|
|
|
*
|
|
|
|
|
* The widget stores the graph, handler, context, and feedback box without
|
|
|
|
|
* taking ownership of any of them.
|
|
|
|
|
*
|
|
|
|
|
* @param x Left coordinate in FLTK screen space.
|
|
|
|
|
* @param y Top coordinate in FLTK screen space.
|
|
|
|
|
* @param width Widget width in pixels.
|
|
|
|
|
* @param height Widget height in pixels.
|
|
|
|
|
* @param graph Borrowed graph snapshot; must outlive the widget's use of it.
|
|
|
|
|
* @param handler Edit callback invoked for every graph mutation.
|
|
|
|
|
* @param context Opaque pointer passed back to @p handler; may be NULL.
|
|
|
|
|
* @param feedback Borrowed label used to report edit results; must outlive the widget.
|
|
|
|
|
*/
|
2026-10-27 01:37:21 +01:00
|
|
|
GraphWidget(int x, int y, int width, int height, const xtc_graph *graph, EditHandler handler, void *context,
|
|
|
|
|
Fl_Box *feedback);
|
|
|
|
|
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Clears cached node positions, selection, and interaction state.
|
|
|
|
|
*
|
|
|
|
|
* Does not change the borrowed graph pointer or the graph's own layout.
|
|
|
|
|
*/
|
2026-10-27 01:37:21 +01:00
|
|
|
void reset_layout()
|
|
|
|
|
{
|
|
|
|
|
count_ = 0;
|
|
|
|
|
selected_node_ = SIZE_MAX;
|
|
|
|
|
edge_selected_ = dragging_ = wiring_ = false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Replaces the graph snapshot and rebuilds cached node positions.
|
|
|
|
|
*
|
|
|
|
|
* Existing positions are preserved by node ID; new nodes are placed on a grid
|
|
|
|
|
* that avoids overlapping previously known nodes. Selections that no longer
|
|
|
|
|
* exist are cleared. The widget does not take ownership of @p graph.
|
|
|
|
|
*
|
|
|
|
|
* @param graph Borrowed graph that must outlive the widget's use of it.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
void set_graph(const xtc_graph *graph);
|
2026-10-27 01:37:21 +01:00
|
|
|
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Returns the graph snapshot currently displayed.
|
|
|
|
|
*
|
|
|
|
|
* The returned pointer is borrowed and valid for GUI thread use only.
|
|
|
|
|
*
|
|
|
|
|
* @return Borrowed graph pointer; may be NULL when none was supplied.
|
|
|
|
|
*/
|
2026-10-27 01:37:21 +01:00
|
|
|
const xtc_graph *graph() const
|
|
|
|
|
{
|
|
|
|
|
return graph_;
|
|
|
|
|
} // Borrowed, GUI thread only.
|
|
|
|
|
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Requests creation of a node through the edit handler.
|
|
|
|
|
*
|
|
|
|
|
* Selects the new node when the edit succeeds.
|
|
|
|
|
*
|
|
|
|
|
* @param type Kind of node to add.
|
|
|
|
|
* @return Result reported by the edit handler.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
xtc_result add_node(xtc_node_type type);
|
2026-10-27 01:58:27 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Requests a connection between two ports through the edit handler.
|
|
|
|
|
*
|
|
|
|
|
* @param connection Connection describing the source and destination node/port pairs.
|
|
|
|
|
* @return Result reported by the edit handler.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
xtc_result connect_ports(const xtc_connection_info &connection);
|
2026-10-27 01:58:27 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Requests removal of the selected wire or node through the edit handler.
|
|
|
|
|
*
|
|
|
|
|
* @return Result reported by the edit handler, or XTC_ERR_INVALID_STATE when
|
|
|
|
|
* nothing is selected.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
xtc_result delete_selected();
|
2026-10-27 01:58:27 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Selects a node by ID and clears any wire selection.
|
|
|
|
|
*
|
|
|
|
|
* @param id Stable node identifier.
|
|
|
|
|
* @return True when the node has a cached position, false otherwise.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
bool select_node(size_t id);
|
2026-10-27 01:58:27 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Selects a wire between two nodes and clears the node selection.
|
|
|
|
|
*
|
|
|
|
|
* @param source Source node identifier.
|
|
|
|
|
* @param destination Destination node identifier.
|
|
|
|
|
* @return True when a matching connection exists, false otherwise.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
bool select_connection(size_t source, size_t destination);
|
2026-10-27 01:58:27 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Moves a node within the widget, clamped to the visible bounds.
|
|
|
|
|
*
|
|
|
|
|
* @param id Stable node identifier.
|
|
|
|
|
* @param left Requested left position in widget-local coordinates.
|
|
|
|
|
* @param top Requested top position in widget-local coordinates.
|
|
|
|
|
* @return True when the node has a cached position, false otherwise.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
bool move_node(size_t id, int left, int top);
|
2026-10-27 01:58:27 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Looks up the cached position of a node.
|
|
|
|
|
*
|
|
|
|
|
* @param id Stable node identifier.
|
|
|
|
|
* @param left Receives the left position in widget-local coordinates.
|
|
|
|
|
* @param top Receives the top position in widget-local coordinates.
|
|
|
|
|
* @return True when the node has a cached position, false otherwise.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
bool node_position(size_t id, int &left, int &top) const;
|
2026-10-27 01:58:27 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Handles mouse and keyboard events for selection, dragging, and wiring.
|
|
|
|
|
*
|
|
|
|
|
* @param event FLTK event code.
|
|
|
|
|
* @return 1 when the event was consumed, 0 for unsupported left-button input,
|
|
|
|
|
* or the result of Fl_Widget::handle() for unhandled events.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
int handle(int event) override;
|
2026-10-27 01:37:21 +01:00
|
|
|
|
|
|
|
|
protected:
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Draws the background, wires, nodes, ports, and any pending wire.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
void draw() override;
|
2026-10-27 01:37:21 +01:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
struct Position {
|
|
|
|
|
size_t id = SIZE_MAX;
|
|
|
|
|
int left = 0, top = 0;
|
|
|
|
|
};
|
|
|
|
|
|
2026-10-26 23:55:35 +01:00
|
|
|
std::array<Position, 256> positions_{};
|
|
|
|
|
size_t count_ = 0U;
|
|
|
|
|
const xtc_graph *graph_;
|
|
|
|
|
EditHandler handler_;
|
|
|
|
|
void *context_;
|
|
|
|
|
Fl_Box *feedback_;
|
|
|
|
|
size_t selected_node_ = SIZE_MAX;
|
|
|
|
|
xtc_connection_info selected_edge_{};
|
|
|
|
|
bool edge_selected_ = false;
|
|
|
|
|
bool dragging_ = false, wiring_ = false;
|
|
|
|
|
size_t wire_node_ = SIZE_MAX, wire_port_ = 0U;
|
|
|
|
|
int drag_x_ = 0, drag_y_ = 0, mouse_x_ = 0, mouse_y_ = 0;
|
2026-10-27 01:58:27 +01:00
|
|
|
/**
|
|
|
|
|
* @brief Shows the outcome of an edit in the feedback label.
|
|
|
|
|
*
|
|
|
|
|
* @param result Result returned by the edit handler.
|
|
|
|
|
* @param success Text shown when @p result is XTC_OK; must be valid then and is ignored otherwise.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
void feedback(xtc_result result, const char *success);
|
2026-10-27 01:58:27 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Finds the port nearest to a widget-local point.
|
|
|
|
|
*
|
|
|
|
|
* Searches nodes in reverse draw order and accepts points within 9 pixels of a
|
|
|
|
|
* port center.
|
|
|
|
|
*
|
|
|
|
|
* @param left Widget-local x coordinate to test.
|
|
|
|
|
* @param top Widget-local y coordinate to test.
|
|
|
|
|
* @param node Receives the matching node ID when true is returned.
|
|
|
|
|
* @param port Receives the matching port index (0 input, 1 output) when true is returned.
|
|
|
|
|
* @return True when a port is within range, false otherwise.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
bool hit_port(int left, int top, size_t &node, size_t &port) const;
|
2026-10-27 01:58:27 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Computes the widget-local center of a node port.
|
|
|
|
|
*
|
|
|
|
|
* @param id Stable node identifier.
|
|
|
|
|
* @param port Port index; 0 selects the input and 1 the output.
|
|
|
|
|
* @param left Receives the port center x coordinate.
|
|
|
|
|
* @param top Receives the port center y coordinate.
|
|
|
|
|
* @return True when the node and port exist, false otherwise.
|
|
|
|
|
*/
|
2026-10-26 23:55:35 +01:00
|
|
|
bool port_position(size_t id, size_t port, int &left, int &top) const;
|
|
|
|
|
};
|
|
|
|
|
#endif
|