Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
graph_editor_view.hpp
Go to the documentation of this file.
1#pragma once
2
3// GraphEditorView — canvas-based node editor on top of pulp::canvas.
4//
5// Renders a SignalGraph as a draggable, connectable node graph. Drag-to-
6// connect creates audio / MIDI / automation / feedback edges (modifier keys
7// distinguish the variants). Live-patchable: every connection mutation
8// invalidates the SignalGraph's CompiledGraph snapshot, so the audio thread
9// transitions through one block of silence and back without tearing.
10//
11// iOS skip: pulp::host is not linked on iOS (App Store policy disallows
12// dlopen of third-party plugins). `__has_include` short-circuits the entire
13// declaration on platforms where the host headers are not on the include
14// path — downstream code that conditionally instantiates GraphEditorView
15// must mirror the same guard.
16//
17// GraphEditorView is an opt-in host-backed widget, not part of the view-only
18// link surface. Desktop consumers that instantiate it must link both
19// `pulp::view` and `pulp::host`.
20
21#if defined(__has_include) && __has_include(<pulp/host/signal_graph.hpp>)
22#define PULP_VIEW_HAS_GRAPH_EDITOR_VIEW 1
23
24#include <functional>
25#include <memory>
29#include <pulp/view/view.hpp>
30#include <string>
31#include <unordered_map>
32
33namespace pulp::view::widgets {
34
35class GraphEditorView : public View {
36 public:
37 explicit GraphEditorView(host::SignalGraph& graph) : graph_(graph) {
38 auto_layout();
39 }
40
41 // Position a node manually (by default add_*_node positions are auto-
42 // assigned in a grid). Persist positions in your own StateTree if you
43 // want them to survive save/load.
44 void set_node_position(host::NodeId id, float x, float y) {
45 positions_[id] = {x, y};
46 }
47
48 // Recompute auto-layout. Called from the constructor; call again after
49 // adding/removing nodes if you don't want to set_node_position manually.
50 void auto_layout();
51
52 void paint(canvas::Canvas& c) override;
53
54 // Use the dispatched legacy mouse callbacks (down/up/drag) so press
55 // and release each get their own entry point — on_mouse_event would
56 // require disambiguating press/release/drag from MouseEvent::is_down,
57 // which platforms set inconsistently during in-flight drags.
58 void on_mouse_down(Point pos) override;
59 void on_mouse_drag(Point pos) override;
60 void on_mouse_up(Point pos) override;
61
62 // Modifier-key state for the next on_mouse_up: the legacy down/up
63 // callbacks pass only Point, so we capture modifiers via on_mouse_event
64 // (which fires before the dispatch to legacy handlers) and consume
65 // them in on_mouse_up to pick the connect variant.
66 void on_mouse_event(const MouseEvent& ev) override;
67 bool wants_mouse_input() const override {
68 return true;
69 }
70
71 enum class EdgeKind { Audio, Midi, Automation, Feedback };
72
73 // ── Hosted plugin editors ───────────────────────────────────────
74 // Open a node's plugin editor, embedded via EditorAttachment. The provider
75 // supplies the WindowHost the editor attaches into — e.g. a floating editor
76 // window the app owns. Without a provider, or for a node with no plugin /
77 // no embeddable editor on this platform, open_node_editor() is a no-op that
78 // returns false. The attachment co-owns the node's PluginSlot, so an open
79 // editor stays valid even if the node is removed from the graph.
80 void set_editor_host_provider(std::function<WindowHost*(host::NodeId)> provider) {
81 editor_host_provider_ = std::move(provider);
82 }
83
86 bool open_node_editor(host::NodeId id);
87
89 void close_node_editor(host::NodeId id);
90
92 bool is_editor_open(host::NodeId id) const {
93 return open_editors_.find(id) != open_editors_.end();
94 }
95
96 private:
97 struct Pos {
98 float x, y;
99 };
100
101 static constexpr float kNodeWidth = 160.0f;
102 static constexpr float kNodeHeight = 80.0f;
103 static constexpr float kPortRadius = 6.0f;
104 static constexpr float kPortSpacing = 18.0f;
105 static constexpr float kCornerRadius = 8.0f;
106
107 // ── Geometry helpers ────────────────────────────────────────────
108 Pos node_origin_(host::NodeId id) const;
109 Pos input_port_pos_(host::NodeId id, int port) const;
110 Pos output_port_pos_(host::NodeId id, int port) const;
111 bool hit_node_(float mx, float my, host::NodeId& out_id) const;
112 bool hit_input_port_(float mx, float my, host::NodeId& out_id, int& out_port) const;
113 bool hit_output_port_(float mx, float my, host::NodeId& out_id, int& out_port) const;
114
115 void paint_node_(canvas::Canvas& c, const host::GraphNode& n);
116 void paint_connection_(canvas::Canvas& c, const host::Connection& conn);
117 void paint_ghost_(canvas::Canvas& c);
118
119 // ── State ───────────────────────────────────────────────────────
120 host::SignalGraph& graph_;
121 std::unordered_map<host::NodeId, Pos> positions_;
122
123 // Drag-to-connect state.
124 bool dragging_ = false;
125 host::NodeId drag_src_node_ = 0;
126 int drag_src_port_ = -1;
127 bool drag_src_is_output_ = true;
128 Pos drag_cursor_{0, 0};
129
130 // Selection (for delete via backspace; not yet wired into key events).
131 host::NodeId selected_node_ = 0;
132
133 // Last-seen modifier state from on_mouse_event, consumed in on_mouse_up
134 // to pick the connect/connect_midi/connect_feedback variant.
135 uint16_t last_modifiers_ = 0;
136
137 // Hosted plugin editors, keyed by node. Each EditorAttachment co-owns the
138 // node's PluginSlot for its lifetime.
139 std::function<WindowHost*(host::NodeId)> editor_host_provider_;
140 std::unordered_map<host::NodeId, std::unique_ptr<EditorAttachment>> open_editors_;
141};
142
143} // namespace pulp::view::widgets
144
145#else // !__has_include(<pulp/host/signal_graph.hpp>)
146#define PULP_VIEW_HAS_GRAPH_EDITOR_VIEW 0
147#endif
@ Audio
±5 V
Definition module_descriptor.hpp:69
@ Automation
Definition graph_runtime_plan.hpp:88
@ Midi
Definition sample_region_proof.hpp:17
@ Feedback
Definition anticipation_eligibility.hpp:26
@ y
Definition input_events.hpp:42
@ x
Definition input_events.hpp:41