Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
hosted_editor_attachment.hpp
Go to the documentation of this file.
1#pragma once
2
3// Hosted-editor → WindowHost attachment.
4//
5// Native-window embedding uses the canonical contract for embedding a
6// platform-native child view inside a Pulp WindowHost via
7// WindowHost::attach_native_child_view / set_native_child_view_bounds /
8// detach_native_child_view. This header migrates the legacy "void* editor
9// handle" hosting path onto that canonical contract.
10//
11// Before: a host walked PluginSlot::create_editor_view() -> void* and was
12// responsible for embedding it into whatever native window it owned,
13// with no typed contract for size, resizability, or detach.
14//
15// After: a host calls EditorAttachment::create(slot, window) and receives an
16// RAII object that owns the slot's HostedEditor and keeps it attached
17// to the WindowHost for its lifetime. Destroying or releasing the
18// attachment detaches the child view and asks the slot to destroy the
19// editor, in the right order, without the host having to thread the
20// native_handle through any glue code itself.
21//
22// EditorAttachment is non-copyable, move-constructible. It is null-safe — if
23// the slot has no editor or the WindowHost has no factory for native child
24// embedding on this platform, create() returns nullptr and the host can fall
25// back to a separate top-level window.
26//
27// Resize: while attached, the attachment OWNS the slot's
28// set_editor_resize_request_handler channel — it installs a handler that
29// re-bounds the child view at the same origin, and clears it on release. A
30// plug-in that asks to resize itself (CLAP gui.request_resize, VST3
31// IPlugFrame::resizeView) therefore moves the window's child view, not just the
32// slot's private container. An application that wants to observe or veto those
33// requests should wrap the attachment rather than install its own handler,
34// which the attachment would overwrite.
35//
36// Lifetime:
37// - Slot: EditorAttachment co-owns the PluginSlot via shared_ptr. The signal
38// graph stores nodes as shared_ptr<PluginSlot>, and a plugin editor
39// routinely outlives the graph node that opened it (the node is removed
40// while its window is still on screen). Sharing ownership means release()
41// can safely call destroy_hosted_editor() even after the graph has dropped
42// its own reference — the slot stays alive until the attachment is gone.
43// - WindowHost: held as a NON-owning pointer. WindowHosts are unique_ptr
44// owned by whatever opened the window and expose no destruction hook, so
45// the caller MUST keep the WindowHost alive until the EditorAttachment is
46// released or destroyed. Destroying the WindowHost first is a use-after-
47// free; release the attachment before tearing its host down.
48
49// iOS skip: pulp::host (and therefore plugin_slot.hpp) is not on the include
50// path on iOS — iOS bundles never host third-party plugins. Use
51// `__has_include` so downstream code can include the header unconditionally
52// and key off PULP_VIEW_HAS_HOSTED_EDITOR_ATTACHMENT.
53#if defined(__has_include) && __has_include(<pulp/host/plugin_slot.hpp>)
54#define PULP_VIEW_HAS_HOSTED_EDITOR_ATTACHMENT 1
55
58
59#include <cstdint>
60#include <memory>
61#include <utility>
62
63namespace pulp::view {
64
65class EditorAttachment {
66 public:
67 EditorAttachment(const EditorAttachment&) = delete;
68 EditorAttachment& operator=(const EditorAttachment&) = delete;
69
70 EditorAttachment(EditorAttachment&& other) noexcept
71 : slot_(std::move(other.slot_)), host_(other.host_), editor_(std::move(other.editor_)),
72 x_(other.x_), y_(other.y_), width_(other.width_), height_(other.height_),
73 attached_(other.attached_) {
74 // slot_ was moved-from, so other.slot_ is already empty.
75 other.host_ = nullptr;
76 other.attached_ = false;
77 // The handler the slot holds captures `this`; the moved-from object is
78 // about to stop being the owner, so re-point it before anyone can call.
79 install_resize_handler();
80 }
81
82 EditorAttachment& operator=(EditorAttachment&& other) noexcept {
83 if (this != &other) {
84 release();
85 slot_ = std::move(other.slot_);
86 host_ = other.host_;
87 editor_ = std::move(other.editor_);
88 x_ = other.x_;
89 y_ = other.y_;
90 width_ = other.width_;
91 height_ = other.height_;
92 attached_ = other.attached_;
93 // slot_ was moved-from, so other.slot_ is already empty.
94 other.host_ = nullptr;
95 other.attached_ = false;
96 install_resize_handler();
97 }
98 return *this;
99 }
100
101 ~EditorAttachment() {
102 release();
103 }
104
110 static std::unique_ptr<EditorAttachment> create(std::shared_ptr<host::PluginSlot> slot,
111 WindowHost* host, float x = 0.0f,
112 float y = 0.0f) {
113 if (!slot || !host)
114 return nullptr;
115 auto editor = slot->create_hosted_editor(host->native_window_handle());
116 if (!editor || !editor->native_handle) {
117 if (editor)
118 slot->destroy_hosted_editor(std::move(editor));
119 return nullptr;
120 }
121 const float w = static_cast<float>(editor->width);
122 const float h = static_cast<float>(editor->height);
123 const bool ok = host->attach_native_child_view(editor->native_handle, x, y, w, h);
124 if (!ok) {
125 slot->destroy_hosted_editor(std::move(editor));
126 return nullptr;
127 }
128 // The constructor is private; std::make_unique can't see it. Use new.
129 std::unique_ptr<EditorAttachment> attachment(
130 new EditorAttachment(std::move(slot), host, std::move(editor), x, y));
131 attachment->install_resize_handler();
132 return attachment;
133 }
134
136 const host::PluginSlot::HostedEditor* editor() const {
137 return editor_.get();
138 }
139
141 bool is_attached() const {
142 return attached_;
143 }
144
148 bool set_bounds(float x, float y, float width, float height) {
149 if (!attached_ || !host_ || !editor_)
150 return false;
151 const bool ok =
152 host_->set_native_child_view_bounds(editor_->native_handle, x, y, width, height);
153 if (ok) {
154 x_ = x;
155 y_ = y;
156 width_ = width;
157 height_ = height;
158 }
159 return ok;
160 }
161
163 void release() {
164 // The slot outlives this object (it is shared), so a handler capturing
165 // `this` must come down before the object does.
166 if (slot_)
167 slot_->set_editor_resize_request_handler(nullptr);
168 if (!attached_) {
169 // Even if we never managed to attach, we still own the editor and
170 // owe the slot a destroy_hosted_editor() call.
171 if (editor_ && slot_) {
172 slot_->destroy_hosted_editor(std::move(editor_));
173 }
174 slot_ = nullptr;
175 host_ = nullptr;
176 return;
177 }
178 if (host_ && editor_) {
179 host_->detach_native_child_view(editor_->native_handle);
180 }
181 attached_ = false;
182 if (slot_ && editor_) {
183 slot_->destroy_hosted_editor(std::move(editor_));
184 }
185 slot_ = nullptr;
186 host_ = nullptr;
187 }
188
189 float x() const {
190 return x_;
191 }
192 float y() const {
193 return y_;
194 }
195 float width() const {
196 return width_;
197 }
198 float height() const {
199 return height_;
200 }
201
202 private:
203 EditorAttachment(std::shared_ptr<host::PluginSlot> slot, WindowHost* host,
204 std::unique_ptr<host::PluginSlot::HostedEditor> editor, float x, float y)
205 : slot_(std::move(slot)), host_(host), editor_(std::move(editor)), x_(x), y_(y),
206 width_(static_cast<float>(editor_ ? editor_->width : 0)),
207 height_(static_cast<float>(editor_ ? editor_->height : 0)), attached_(true) {}
208
214 void install_resize_handler() {
215 if (!slot_)
216 return;
217 slot_->set_editor_resize_request_handler([this](uint32_t width, uint32_t height) {
218 return set_bounds(x_, y_, static_cast<float>(width), static_cast<float>(height));
219 });
220 }
221
222 std::shared_ptr<host::PluginSlot> slot_;
223 WindowHost* host_ = nullptr;
224 std::unique_ptr<host::PluginSlot::HostedEditor> editor_;
225 float x_ = 0.0f;
226 float y_ = 0.0f;
227 float width_ = 0.0f;
228 float height_ = 0.0f;
229 bool attached_ = false;
230};
231
232} // namespace pulp::view
233
234#else // !__has_include(<pulp/host/plugin_slot.hpp>)
235#define PULP_VIEW_HAS_HOSTED_EDITOR_ATTACHMENT 0
236#endif
@ ok
Definition analyzer_provider.hpp:58
@ release
Release only: voices enter their normal release and tails ring out (MIDI CC 123 semantic).
Definition panic.hpp:39
@ editor
the main plug-in or app editor
Definition editor_host.hpp:308
@ other
Definition udev_monitor.hpp:26
@ width
Definition config.hpp:225
@ move
Definition web_event_translate.hpp:110
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
@ h
Definition input_events.hpp:39
@ y
Definition input_events.hpp:42
@ x
Definition input_events.hpp:41
@ w
Definition input_events.hpp:41
@ height
Definition css_animation.hpp:128