truce_rack_core/editor.rs
1//! Host-side editor (GUI) hosting interface.
2//!
3//! Plugins that ship a custom editor expose it through their
4//! format's GUI API: `clap.gui`, `kAudioUnitProperty_CocoaUI`,
5//! `IEditController::createView`, LV2 `ui:UI`. truce-rack-core wraps
6//! these behind a single [`PluginEditor`] trait so a host doesn't
7//! care which format produced the editor — it only needs a
8//! native parent window handle and a place to put the resulting
9//! view.
10//!
11//! # Threading
12//!
13//! Editor methods run on the **main (UI) thread**. Audio
14//! processing (the `Plugin::process` path) runs on the audio
15//! thread. The host application is responsible for serialising
16//! the two — never invoke editor methods while the audio thread
17//! holds a `&mut PluginCore`. Rust's borrow rules enforce this
18//! because [`crate::PluginCore::editor`] borrows `&mut self`.
19//!
20//! # Platform handles
21//!
22//! Editors attach to a native parent window via a
23//! [`WindowHandle`]. The variant tells the wrapper which API to
24//! use:
25//!
26//! - macOS: [`WindowHandle::NSView`] — pointer to an `NSView*`.
27//! - Windows: [`WindowHandle::HWND`] — `HWND`.
28//! - Linux X11: [`WindowHandle::X11`] — the X11 window ID.
29//!
30//! Wayland support is currently unwired; CLAP also defines
31//! a Wayland API but few hosts implement it.
32
33use crate::error::Result;
34use std::ffi::c_void;
35
36/// Native parent window the plugin's editor view attaches to.
37///
38/// The host opens its own window, picks the appropriate variant
39/// for the platform, and hands it to [`PluginEditor::open`]. The
40/// plugin embeds its view inside that parent — the host stays in
41/// charge of the outer window's lifecycle.
42#[derive(Debug, Clone, Copy)]
43pub enum WindowHandle {
44 /// macOS / iOS / visionOS — pointer to a parent `NSView*`.
45 NSView(*mut c_void),
46 /// Windows — parent `HWND` (cast through `*mut c_void` to
47 /// avoid pulling in the windows-sys dep at this layer).
48 HWND(*mut c_void),
49 /// X11 — the X11 `Window` id (`unsigned long` on most
50 /// platforms, here widened to `u64`).
51 X11(u64),
52}
53
54/// Editor-side view of a hosted plugin's UI.
55///
56/// Created by [`crate::PluginCore::editor`] when a plugin reports a
57/// custom editor (its format-specific GUI extension is present
58/// and `is_api_supported` returns true for the platform's API).
59/// Methods correspond to the union of `clap.gui`, AU's
60/// `kAudioUnitProperty_CocoaUI`, and VST3's `IPlugView`.
61///
62/// All methods run on the main (UI) thread; see the module
63/// docs.
64pub trait PluginEditor {
65 /// Open the editor inside `parent`. After this returns `Ok`
66 /// the editor is visible (or ready to be shown via
67 /// [`Self::show`] for formats that distinguish the two
68 /// phases). `scale` is the host's UI scale factor — 1.0 for
69 /// non-Retina, 2.0 for typical Retina, etc.
70 ///
71 /// # Errors
72 /// Returns [`crate::Error::Other`] when the underlying format
73 /// API returns failure, e.g. the plugin's editor doesn't
74 /// support the platform's window API.
75 fn open(&mut self, parent: WindowHandle, scale: f64) -> Result<()>;
76
77 /// Close the editor. Releases the plugin's view but does not
78 /// invalidate the editor itself — calling [`Self::open`]
79 /// again is legal.
80 fn close(&mut self);
81
82 /// `true` while [`Self::open`] has succeeded and
83 /// [`Self::close`] hasn't been called.
84 fn is_open(&self) -> bool;
85
86 /// Editor's current logical size in pixels. `None` if the
87 /// plugin doesn't expose a size (rare but legal — some AU
88 /// editors leave the host to query the `NSView`'s `frame`).
89 fn size(&self) -> Option<(u32, u32)>;
90
91 /// `true` if the editor lets the host resize it.
92 fn is_resizable(&self) -> bool;
93
94 /// Request a new size. Plugins with aspect-ratio or
95 /// minimum-size constraints may pick a nearby size — the
96 /// return value is what the plugin actually adopted. Returns
97 /// `None` on failure.
98 fn set_size(&mut self, width: u32, height: u32) -> Option<(u32, u32)>;
99
100 /// Show the editor view. Only meaningful for CLAP (which
101 /// separates `create` and `show`); other formats no-op.
102 fn show(&mut self);
103
104 /// Hide the editor view without destroying it.
105 fn hide(&mut self);
106
107 /// Per-frame hook the host calls on the UI thread (typically
108 /// at the parent window's frame rate). Formats whose plugins
109 /// expose an idle / animation callback (LV2 `ui:idleInterface`,
110 /// CLAP `gui.suggest_title`, future AU `GestureKit` ticks)
111 /// override this to drive their plugin. Default is a no-op
112 /// for formats that don't need it.
113 fn on_idle(&mut self) {}
114}